SlavedUpdate.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  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: SlavedUpdate.cpp /////////////////////////////////////////////////////////////////////////
  24. // Author: Matt Campbell, March 2002
  25. // Updated: Kris Morness, July 2002 -- Add support for advanced scout drone abilities
  26. // Desc: Slaved unit(s) remain close to their master. Used by scout drones, and used by stinger
  27. // soldiers that are close to a stinger site. It's important to note that any slaved units
  28. // can use any or all features, some of which are specialized.
  29. ///////////////////////////////////////////////////////////////////////////////////////////////////
  30. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  31. #include "Common/RandomValue.h"
  32. #include "Common/Xfer.h"
  33. #include "Common/Team.h"
  34. #include "Common/MiscAudio.h"
  35. #include "GameClient/Drawable.h"
  36. #include "GameClient/ParticleSys.h"
  37. #include "GameLogic/AIPathfind.h"
  38. #include "GameLogic/Damage.h"
  39. #include "GameLogic/GameLogic.h"
  40. #include "GameLogic/Locomotor.h"
  41. #include "GameLogic/Object.h"
  42. #include "GameLogic/PartitionManager.h"
  43. #include "GameLogic/TerrainLogic.h"
  44. #include "GameLogic/Module/AIUpdate.h"
  45. #include "GameLogic/Module/BodyModule.h"
  46. #include "GameLogic/Module/SlavedUpdate.h"
  47. #include "GameLogic/Weapon.h"
  48. #ifdef _INTERNAL
  49. // for occasional debugging...
  50. //#pragma optimize("", off)
  51. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  52. #endif
  53. #define STRAY_MULTIPLIER 2.0f // Multiplier from stating diestance from tunnel, to max distance from
  54. const Real CLOSE_ENOUGH = 15; // Our moveTo commands and pathfinding can't handle people in the way, so quit trying to hump someone on your spot
  55. const Real CLOSE_ENOUGH_SQR = (CLOSE_ENOUGH * CLOSE_ENOUGH);
  56. //-------------------------------------------------------------------------------------------------
  57. SlavedUpdate::SlavedUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
  58. {
  59. // const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  60. m_slaver = INVALID_ID;
  61. m_guardPointOffset.zero();
  62. m_framesToWait = 0;
  63. m_repairState = REPAIRSTATE_NONE;
  64. m_repairing = false;
  65. }
  66. //-------------------------------------------------------------------------------------------------
  67. SlavedUpdate::~SlavedUpdate( void )
  68. {
  69. }
  70. //-------------------------------------------------------------------------------------------------
  71. void SlavedUpdate::onObjectCreated()
  72. {
  73. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  74. if( data->m_repairRatePerSecond > 0.0f )
  75. {
  76. //If this object can repair, pack it up at init.
  77. getObject()->setModelConditionState( MODELCONDITION_PACKING );
  78. }
  79. }
  80. //-------------------------------------------------------------------------------------------------
  81. void SlavedUpdate::onEnslave( const Object *slaver )
  82. {
  83. startSlavedEffects( slaver );
  84. }
  85. //-------------------------------------------------------------------------------------------------
  86. void SlavedUpdate::onSlaverDie( const DamageInfo *info )
  87. {
  88. stopSlavedEffects();
  89. }
  90. //-------------------------------------------------------------------------------------------------
  91. void SlavedUpdate::onSlaverDamage( const DamageInfo *info )
  92. {
  93. // Only slaves with a ProneUpdate will even care.
  94. AIUpdateInterface *ai = getObject()->getAIUpdateInterface();
  95. if( ai )
  96. ai->aiGoProne( info, CMD_FROM_AI );
  97. }
  98. //-------------------------------------------------------------------------------------------------
  99. UpdateSleepTime SlavedUpdate::update( void )
  100. {
  101. /// @todo srj use SLEEPY_UPDATE here
  102. if( m_framesToWait > 0 )
  103. {
  104. m_framesToWait--;
  105. }
  106. if( m_repairState == REPAIRSTATE_NONE )
  107. {
  108. if( m_framesToWait > 0 )
  109. {
  110. return UPDATE_SLEEP_NONE;
  111. }
  112. m_framesToWait = SLAVED_UPDATE_RATE;
  113. }
  114. if( m_slaver == INVALID_ID )
  115. return UPDATE_SLEEP_NONE;
  116. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  117. Object *me = getObject();
  118. if( !me )
  119. {
  120. return UPDATE_SLEEP_NONE;
  121. }
  122. AIUpdateInterface *myAI = me->getAIUpdateInterface();
  123. if( !myAI )
  124. {
  125. return UPDATE_SLEEP_NONE;
  126. }
  127. Locomotor *locomotor = myAI->getCurLocomotor();
  128. if( !locomotor )
  129. {
  130. return UPDATE_SLEEP_NONE;
  131. }
  132. Object *master = TheGameLogic->findObjectByID( m_slaver );
  133. if( !master || master->isEffectivelyDead() || master->isDisabledByType( DISABLED_UNMANNED ) )
  134. {
  135. stopSlavedEffects();
  136. //Killing is lame.
  137. //me->kill();
  138. //Let's disable the drone so it crashes instead!
  139. //Added special case code in physics falling to ensure death.
  140. me->setDisabled( DISABLED_UNMANNED );
  141. if ( me->getAI() )
  142. me->getAI()->aiIdle( CMD_FROM_AI);
  143. return UPDATE_SLEEP_NONE;
  144. }
  145. else
  146. {
  147. Team *masterTeam = master->getTeam();
  148. Team *myTeam = me->getTeam();
  149. if ( masterTeam->getRelationship( myTeam ) != ALLIES )
  150. {//slaver must have been hijacked or something.. // we will join his team
  151. me->defect( masterTeam, 0 );
  152. }
  153. }
  154. if (data->m_stayOnSameLayerAsMaster)
  155. me->setLayer(master->getLayer());
  156. //Clear the drone spotting bonus to the master. Up to the drone
  157. //to satisfy the conditions to set it again for the next update.
  158. master->clearWeaponBonusCondition( WEAPONBONUSCONDITION_DRONE_SPOTTING );
  159. //Get my master's AI. If he is attacking something, grant him a range bonus,
  160. //and I'll fly over the target.
  161. Object *target = NULL;
  162. AIUpdateInterface *masterAI = master->getAIUpdateInterface();
  163. if( masterAI )
  164. {
  165. target = masterAI->getCurrentVictim();
  166. }
  167. //Calculate the health percentage of the master -- there are two places we care.
  168. //NOTE: Health percentage will always be 100 should the drone be incapable of
  169. //repairing.
  170. Int healthPercentage = 100;
  171. if( data->m_repairRatePerSecond > 0.0f )
  172. {
  173. BodyModuleInterface *body = master->getBodyModule();
  174. if( body )
  175. {
  176. Real health = body->getHealth();
  177. Real maxHealth = body->getMaxHealth();
  178. healthPercentage = (Int)(health / maxHealth * 100.0f);
  179. }
  180. }
  181. //Determine whether or not we need to go back to the master to repair him
  182. if( healthPercentage <= data->m_repairWhenHealthBelowPercentage )
  183. {
  184. //1ST PRIORITY: Go to the master's position to repair him because he needs it.
  185. doRepairLogic();
  186. return UPDATE_SLEEP_NONE;
  187. }
  188. if( data->m_attackRange )
  189. {
  190. //2ND PRIORITY: Go to the master's current victim (as close as wander distance allows)
  191. if( target )
  192. {
  193. //At this point, we officially are in an attack mode! Now, simply
  194. endRepair();
  195. doAttackLogic( target );
  196. return UPDATE_SLEEP_NONE;
  197. }
  198. }
  199. if( data->m_scoutRange )
  200. {
  201. //3RD PRIORITY: Hover above master's current move destination (as close as wander distance
  202. //allows).
  203. if( masterAI->getPath() )
  204. {
  205. const Coord3D *masterDest = masterAI->getPath()->getLastNode()->getPosition();
  206. //Check to see if master is close to the goal position.
  207. Real distSqr = ThePartitionManager->getDistanceSquared( master, masterDest, FROM_BOUNDINGSPHERE_2D );
  208. if( distSqr > (data->m_guardMaxRange * 0.5f) * (data->m_guardMaxRange * 0.5f) )
  209. {
  210. //If the master's distance to destination is more than half of the guarding range of the slave,
  211. //then order the slave to scout it.
  212. endRepair();
  213. doScoutLogic( masterDest );
  214. return UPDATE_SLEEP_NONE;
  215. }
  216. }
  217. }
  218. //NOTE: Health percentage will always be 100 should the drone be incapable of
  219. //repairing.
  220. if( healthPercentage < 100 )
  221. {
  222. //3RD PRIORITY: Go to the master's position to repair him (because we're idle)
  223. doRepairLogic();
  224. return UPDATE_SLEEP_NONE;
  225. }
  226. // update our "pinned" location based on where our master is
  227. Coord3D pinnedPosition = *master->getPosition();
  228. pinnedPosition.x += m_guardPointOffset.x;
  229. pinnedPosition.y += m_guardPointOffset.y;
  230. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( pinnedPosition.x, pinnedPosition.y );
  231. if( data->m_guardMaxRange )
  232. {
  233. //3RD PRIORITY: Guard the master's area.
  234. if( myAI->isIdle() && ThePartitionManager->getDistanceSquared(me, &pinnedPosition, FROM_CENTER_3D) > CLOSE_ENOUGH_SQR )
  235. {
  236. //I'm idle and too far away.
  237. endRepair();
  238. doGuardLogic( &pinnedPosition );
  239. }
  240. else if( ThePartitionManager->getDistanceSquared( me, master, FROM_CENTER_3D ) > sqr(STRAY_MULTIPLIER * data->m_guardMaxRange ) )
  241. {
  242. //I'm too far away, no matter what I'm doing.
  243. endRepair();
  244. doGuardLogic( &pinnedPosition );
  245. }
  246. }
  247. return UPDATE_SLEEP_NONE;
  248. }
  249. //-------------------------------------------------------------------------------------------------
  250. // We are ordered to attempt to get as close as possible to my master's target.
  251. //-------------------------------------------------------------------------------------------------
  252. void SlavedUpdate::doAttackLogic( const Object *target )
  253. {
  254. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  255. Object *me = getObject();
  256. Object *master = TheGameLogic->findObjectByID( m_slaver );
  257. Coord3D attackPosition;
  258. //First, determine the attack position. If the target is too far away, then we'll
  259. //calculate the closest allowable position.
  260. const Coord3D *targetPos = target->getPosition();
  261. Real dist = ThePartitionManager->getDistanceSquared( me, targetPos, FROM_BOUNDINGSPHERE_2D );
  262. if( dist > sqr( data->m_attackRange ) )
  263. {
  264. //The distance is too far, so calculate the best allowable position.
  265. Coord3D vector;
  266. vector.set( targetPos );
  267. vector.sub( master->getPosition() );
  268. vector.normalize();
  269. vector.scale( data->m_attackRange );
  270. //Now that we have calculated the vector relative to me, add it to my position to get my goal.
  271. attackPosition.set( master->getPosition() );
  272. attackPosition.add( &vector );
  273. }
  274. else
  275. {
  276. //We are close enough, so use the target position -- easy!
  277. attackPosition.set( targetPos );
  278. }
  279. //Finally, if we have a wander distance, then randomly select a point within
  280. //the wander range radius of the pinned position, and we'll go there.
  281. if( data->m_attackWanderRange )
  282. {
  283. //Allow me to wander away from the pinnedPosition.
  284. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  285. m_guardPointOffset.zero();
  286. m_guardPointOffset.x += data->m_attackWanderRange * Cos( randomDirection );
  287. m_guardPointOffset.y += data->m_attackWanderRange * Sin( randomDirection );
  288. //Offset our pinned position by our random offset.
  289. attackPosition.x += m_guardPointOffset.x;
  290. attackPosition.y += m_guardPointOffset.y;
  291. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( attackPosition.x, attackPosition.y );
  292. }
  293. //Move to the updated position!
  294. AIUpdateInterface *ai = me->getAIUpdateInterface();
  295. if( ai )
  296. {
  297. ai->aiMoveToPosition( &attackPosition, CMD_FROM_AI );
  298. }
  299. if( dist < sqr( data->m_distToTargetToGrantRangeBonus ) )
  300. {
  301. //Finally, seeing we are close enough to the target, grant our
  302. //master extended weapon range!
  303. master->setWeaponBonusCondition( WEAPONBONUSCONDITION_DRONE_SPOTTING );
  304. }
  305. }
  306. //-------------------------------------------------------------------------------------------------
  307. // We are ordered to attempt to get as close as possible to my master's movement destination point.
  308. //-------------------------------------------------------------------------------------------------
  309. void SlavedUpdate::doScoutLogic( const Coord3D *mastersDestination )
  310. {
  311. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  312. Object *me = getObject();
  313. Object *master = TheGameLogic->findObjectByID( m_slaver );
  314. Coord3D scoutPosition;
  315. //First, determine the scout position. If our master's destination is too far away, then we'll
  316. //calculate the closest allowable position.
  317. Real dist = ThePartitionManager->getDistanceSquared( me, mastersDestination, FROM_BOUNDINGSPHERE_2D );
  318. if( dist > sqr( data->m_scoutRange ) )
  319. {
  320. //The distance is too far, so calculate the best allowable position.
  321. Coord3D vector;
  322. vector.set( mastersDestination );
  323. vector.sub( master->getPosition() );
  324. vector.normalize();
  325. vector.scale( data->m_scoutRange );
  326. //Now that we have calculated the vector relative to me, add it to my position to get my goal.
  327. scoutPosition.set( master->getPosition() );
  328. scoutPosition.add( &vector );
  329. }
  330. else
  331. {
  332. //We are close enough, so use the target position -- easy!
  333. scoutPosition.set( mastersDestination );
  334. }
  335. //Finally, if we have a wander distance, then randomly select a point within
  336. //the wander range radius of the pinned position, and we'll go there.
  337. if( data->m_scoutWanderRange )
  338. {
  339. //Allow me to wander away from the pinnedPosition.
  340. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  341. m_guardPointOffset.zero();
  342. m_guardPointOffset.x += data->m_scoutWanderRange * Cos( randomDirection );
  343. m_guardPointOffset.y += data->m_scoutWanderRange * Sin( randomDirection );
  344. //Offset our pinned position by our random offset.
  345. scoutPosition.x += m_guardPointOffset.x;
  346. scoutPosition.y += m_guardPointOffset.y;
  347. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( scoutPosition.x, scoutPosition.y );
  348. }
  349. //Move to the updated position!
  350. AIUpdateInterface *ai = me->getAIUpdateInterface();
  351. if( ai )
  352. {
  353. ai->aiMoveToPosition( &scoutPosition, CMD_FROM_AI );
  354. }
  355. }
  356. //-------------------------------------------------------------------------------------------------
  357. // We are ordered to attempt to get as close as possible to my master's position.
  358. //-------------------------------------------------------------------------------------------------
  359. void SlavedUpdate::doGuardLogic( Coord3D *pinnedPosition )
  360. {
  361. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  362. Object *me = getObject();
  363. if( data->m_guardWanderRange )
  364. {
  365. // recalc where we want to be if we wander around
  366. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  367. m_guardPointOffset.zero();
  368. m_guardPointOffset.x += data->m_guardMaxRange * Cos( randomDirection );
  369. m_guardPointOffset.y += data->m_guardMaxRange * Sin( randomDirection );
  370. pinnedPosition->x += m_guardPointOffset.x;
  371. pinnedPosition->y += m_guardPointOffset.y;
  372. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( pinnedPosition->x, pinnedPosition->y );
  373. }
  374. AIUpdateInterface *ai = me->getAIUpdateInterface();
  375. if( ai )
  376. {
  377. ai->aiMoveToPosition( pinnedPosition, CMD_FROM_AI );
  378. }
  379. }
  380. //-------------------------------------------------------------------------------------------------
  381. // We are ordered to repair our master
  382. //-------------------------------------------------------------------------------------------------
  383. void SlavedUpdate::doRepairLogic()
  384. {
  385. Object *me = getObject();
  386. Object *master = TheGameLogic->findObjectByID( m_slaver );
  387. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  388. AIUpdateInterface *ai = me->getAIUpdateInterface();
  389. if( !ai )
  390. {
  391. return;
  392. }
  393. //There are two major things... either move closer or repair.
  394. Real distSqr = ThePartitionManager->getDistanceSquared( me, master, FROM_BOUNDINGSPHERE_2D );
  395. Bool closeEnough = distSqr < 12.0f * 12.0f;
  396. //We're going to do different things based on the repair state.
  397. if( closeEnough )
  398. {
  399. //Now drive the repairing states.
  400. switch( m_repairState )
  401. {
  402. case REPAIRSTATE_NONE:
  403. setRepairState( REPAIRSTATE_READY );
  404. break;
  405. case REPAIRSTATE_READY:
  406. case REPAIRSTATE_EXTENDING:
  407. if( m_framesToWait == 0 )
  408. {
  409. setRepairState( REPAIRSTATE_WELDING );
  410. }
  411. break;
  412. case REPAIRSTATE_UNPACKING:
  413. case REPAIRSTATE_WELDING:
  414. case REPAIRSTATE_RETRACTING:
  415. if( m_framesToWait == 0 )
  416. {
  417. setRepairState( REPAIRSTATE_READY );
  418. }
  419. break;
  420. }
  421. }
  422. else
  423. {
  424. m_repairing = false;
  425. Bool closeEnoughForZPrecision = distSqr < sqr(master->getGeometryInfo().getBoundingSphereRadius() * 2);
  426. //We're too far away to repair, so get closer.
  427. Locomotor *locomotor = ai->getCurLocomotor();
  428. if( locomotor )
  429. {
  430. locomotor->setUsePreciseZPos( closeEnoughForZPrecision );
  431. }
  432. Coord3D pos;
  433. pos.set( master->getPosition() );
  434. Real altitude = GameLogicRandomValueReal( data->m_repairMinAltitude, data->m_repairMaxAltitude );
  435. pos.z += altitude;
  436. ai->aiMoveToPosition( &pos, CMD_FROM_AI );
  437. //Also speed things up by retracting the repair arm (so viewers
  438. //will see it's intentions)
  439. if( m_framesToWait == 0 )
  440. {
  441. setRepairState( REPAIRSTATE_READY );
  442. }
  443. }
  444. if( closeEnough && m_repairing )
  445. {
  446. //We're close enough to repair.
  447. BodyModuleInterface *body = master->getBodyModule();
  448. if( body )
  449. {
  450. //Calculate the repair rate per frame.
  451. Real repairAmount = data->m_repairRatePerSecond / LOGICFRAMES_PER_SECOND;
  452. DamageInfo healingInfo;
  453. healingInfo.in.m_amount = repairAmount;
  454. healingInfo.in.m_damageType = DAMAGE_HEALING;
  455. healingInfo.in.m_deathType = DEATH_NONE;
  456. body->attemptHealing( &healingInfo );
  457. }
  458. }
  459. }
  460. //-------------------------------------------------------------------------------------------------
  461. void SlavedUpdate::endRepair()
  462. {
  463. if( m_repairState != REPAIRSTATE_NONE )
  464. {
  465. //Remove the state so we can start using the update timer again.
  466. m_repairState = REPAIRSTATE_NONE;
  467. m_framesToWait = SLAVED_UPDATE_RATE;
  468. m_repairing = false;
  469. //Set the packing state and clear any other states (to force the arm to fully retract)
  470. setRepairModelConditionStates( MODELCONDITION_PACKING );
  471. }
  472. AIUpdateInterface* ai = getObject()->getAIUpdateInterface();
  473. if( ai )
  474. {
  475. ai->chooseLocomotorSet(LOCOMOTORSET_NORMAL);
  476. Locomotor* locomotor = ai->getCurLocomotor();
  477. if (locomotor)
  478. {
  479. locomotor->setUltraAccurate(false);
  480. locomotor->setUsePreciseZPos(false);
  481. }
  482. }
  483. }
  484. //-------------------------------------------------------------------------------------------------
  485. void SlavedUpdate::setRepairModelConditionStates( ModelConditionFlagType flag )
  486. {
  487. Object *obj = getObject();
  488. obj->clearModelConditionState( MODELCONDITION_PACKING );
  489. obj->clearModelConditionState( MODELCONDITION_UNPACKING );
  490. obj->clearModelConditionState( MODELCONDITION_FIRING_B );
  491. obj->clearModelConditionState( MODELCONDITION_FIRING_C );
  492. obj->clearModelConditionState( MODELCONDITION_BETWEEN_FIRING_SHOTS_B );
  493. obj->clearModelConditionState( MODELCONDITION_BETWEEN_FIRING_SHOTS_C );
  494. obj->clearModelConditionState( MODELCONDITION_RELOADING_B );
  495. obj->clearModelConditionState( MODELCONDITION_RELOADING_C );
  496. obj->setModelConditionState( flag );
  497. }
  498. //-------------------------------------------------------------------------------------------------
  499. void SlavedUpdate::setRepairState( RepairStates repairState )
  500. {
  501. Object *obj = getObject();
  502. Drawable *draw = obj->getDrawable();
  503. //Object *master = TheGameLogic->findObjectByID( m_slaver );
  504. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  505. if( repairState == m_repairState )
  506. {
  507. return;
  508. }
  509. switch( repairState )
  510. {
  511. case REPAIRSTATE_UNPACKING:
  512. setRepairModelConditionStates( MODELCONDITION_UNPACKING );
  513. m_framesToWait = 15;
  514. break;
  515. case REPAIRSTATE_PACKING:
  516. setRepairModelConditionStates( MODELCONDITION_PACKING );
  517. m_framesToWait = 15;
  518. break;
  519. case REPAIRSTATE_READY:
  520. {
  521. switch( m_repairState )
  522. {
  523. case REPAIRSTATE_NONE:
  524. //We're not in a repair state -- so assume we need to unpack
  525. setRepairModelConditionStates( MODELCONDITION_UNPACKING );
  526. m_repairState = REPAIRSTATE_UNPACKING;
  527. m_framesToWait = 15;
  528. break;
  529. case REPAIRSTATE_WELDING:
  530. //We are welding so retract before going into ready state.
  531. m_repairState = REPAIRSTATE_RETRACTING;
  532. m_framesToWait = 5;
  533. setRepairModelConditionStates( MODELCONDITION_FIRING_C );
  534. moveToNewRepairSpot();
  535. break;
  536. default:
  537. m_repairState = REPAIRSTATE_READY;
  538. m_framesToWait = GameLogicRandomValue( data->m_minReadyFrames, data->m_maxReadyFrames );
  539. break;
  540. }
  541. break;
  542. }
  543. case REPAIRSTATE_WELDING:
  544. {
  545. if( m_repairState == REPAIRSTATE_READY )
  546. {
  547. m_repairState = REPAIRSTATE_EXTENDING;
  548. m_framesToWait = 5;
  549. setRepairModelConditionStates( MODELCONDITION_FIRING_B );
  550. break;
  551. }
  552. else
  553. {
  554. m_repairState = REPAIRSTATE_WELDING;
  555. m_framesToWait = GameLogicRandomValue( data->m_minWeldFrames, data->m_maxWeldFrames );
  556. //Make sparks!
  557. if( !data->m_weldingSysName.isEmpty() )
  558. {
  559. const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_weldingSysName );
  560. if( tmp )
  561. {
  562. ParticleSystem *weldingSys = TheParticleSystemManager->createParticleSystem(tmp);
  563. if( weldingSys )
  564. {
  565. Coord3D pos;
  566. //Get the bone position
  567. if( draw->getPristineBonePositions( data->m_weldingFXBone.str(), 0, &pos, NULL, 1 ) )
  568. {
  569. pos.add( obj->getPosition() );
  570. }
  571. else
  572. {
  573. pos.set( obj->getPosition() );
  574. }
  575. weldingSys->setPosition( &pos );
  576. Real time = (Real)(m_framesToWait * LOGICFRAMES_PER_SECOND);
  577. weldingSys->setLifetimeRange( time, time );
  578. AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_repairSparks;
  579. soundToPlay.setPosition( &pos );
  580. TheAudio->addAudioEvent( &soundToPlay );
  581. }
  582. }
  583. }
  584. if( !m_repairing )
  585. {
  586. //Don't actually start healing until we fire our first sparks
  587. m_repairing = true;
  588. AIUpdateInterface *ai = obj->getAIUpdateInterface();
  589. if( ai )
  590. {
  591. //ai->aiFaceObject( master, CMD_FROM_AI );
  592. }
  593. }
  594. break;
  595. }
  596. break;
  597. }
  598. }
  599. }
  600. //-------------------------------------------------------------------------------------------------
  601. void SlavedUpdate::moveToNewRepairSpot()
  602. {
  603. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  604. Object *me = getObject();
  605. Object *master = TheGameLogic->findObjectByID( m_slaver );
  606. //Finally, if we have a wander distance, then randomly select a point within
  607. //the wander range radius of the pinned position, and we'll go there.
  608. if( data->m_repairRange )
  609. {
  610. //Allow me to wander away from the pinnedPosition.
  611. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  612. m_guardPointOffset.set( master->getPosition() );
  613. m_guardPointOffset.x += data->m_repairRange * Cos( randomDirection );
  614. m_guardPointOffset.y += data->m_repairRange * Sin( randomDirection );
  615. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( m_guardPointOffset.x, m_guardPointOffset.y );
  616. Real altitude = GameLogicRandomValueReal( data->m_repairMinAltitude, data->m_repairMaxAltitude );
  617. m_guardPointOffset.z += altitude;
  618. AIUpdateInterface *ai = me->getAIUpdateInterface();
  619. if( ai )
  620. {
  621. ai->chooseLocomotorSet( LOCOMOTORSET_PANIC );
  622. ai->getCurLocomotor()->setUltraAccurate( true );
  623. ai->aiMoveToPosition( &m_guardPointOffset, CMD_FROM_AI );
  624. Locomotor *locomotor = ai->getCurLocomotor();
  625. if( locomotor )
  626. {
  627. locomotor->setUsePreciseZPos( true );
  628. }
  629. }
  630. }
  631. }
  632. //-------------------------------------------------------------------------------------------------
  633. void SlavedUpdate::startSlavedEffects( const Object *slaver )
  634. {
  635. if( slaver == NULL )
  636. return;
  637. m_slaver = slaver->getID();
  638. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  639. // Decide where our pinned stray point is
  640. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  641. m_guardPointOffset.zero();
  642. m_guardPointOffset.x += data->m_guardMaxRange * Cos( randomDirection );
  643. m_guardPointOffset.y += data->m_guardMaxRange * Sin( randomDirection );
  644. // mark selves as not selectable
  645. getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) );
  646. if ( slaver->testStatus( OBJECT_STATUS_STEALTHED ) )
  647. {
  648. StealthUpdate *myStealth = getObject()->getStealth();
  649. if ( myStealth )
  650. {
  651. myStealth->receiveGrant( true );
  652. // note to anyone... once stealth is granted to this drone(or such)
  653. // let its own stealthupdate govern the allowedtostealth cases
  654. }
  655. }
  656. }
  657. //-------------------------------------------------------------------------------------------------
  658. void SlavedUpdate::stopSlavedEffects()
  659. {
  660. m_slaver = INVALID_ID;
  661. m_guardPointOffset.zero();
  662. /// @todo Just a thought. Our Status bits on objects really need to be reference counts so you don't clear someone else's flag
  663. getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) );
  664. getObject()->clearDisabled( DISABLED_HELD );
  665. }
  666. // ------------------------------------------------------------------------------------------------
  667. /** CRC */
  668. // ------------------------------------------------------------------------------------------------
  669. void SlavedUpdate::crc( Xfer *xfer )
  670. {
  671. // extend base class
  672. UpdateModule::crc( xfer );
  673. } // end crc
  674. // ------------------------------------------------------------------------------------------------
  675. /** Xfer method
  676. * Version Info:
  677. * 1: Initial version */
  678. // ------------------------------------------------------------------------------------------------
  679. void SlavedUpdate::xfer( Xfer *xfer )
  680. {
  681. // version
  682. XferVersion currentVersion = 1;
  683. XferVersion version = currentVersion;
  684. xfer->xferVersion( &version, currentVersion );
  685. // extend base class
  686. UpdateModule::xfer( xfer );
  687. // slaver
  688. xfer->xferObjectID( &m_slaver );
  689. // guard point offset
  690. xfer->xferCoord3D( &m_guardPointOffset);
  691. // frames to wait
  692. xfer->xferInt( &m_framesToWait );
  693. // repair state
  694. xfer->xferUser( &m_repairState, sizeof( RepairStates ) );
  695. // repairing
  696. xfer->xferBool( &m_repairing );
  697. } // end xfer
  698. // ------------------------------------------------------------------------------------------------
  699. /** Load post process */
  700. // ------------------------------------------------------------------------------------------------
  701. void SlavedUpdate::loadPostProcess( void )
  702. {
  703. // extend base class
  704. UpdateModule::loadPostProcess();
  705. } // end loadPostProcess