SlavedUpdate.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  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: 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. return UPDATE_SLEEP_NONE;
  142. }
  143. else
  144. {
  145. Team *masterTeam = master->getTeam();
  146. Team *myTeam = me->getTeam();
  147. if ( masterTeam->getRelationship( myTeam ) != ALLIES )
  148. {//slaver must have been hijacked or something.. // we will join his team
  149. me->defect( masterTeam, 0 );
  150. }
  151. }
  152. if (data->m_stayOnSameLayerAsMaster)
  153. me->setLayer(master->getLayer());
  154. //Clear the drone spotting bonus to the master. Up to the drone
  155. //to satisfy the conditions to set it again for the next update.
  156. master->clearWeaponBonusCondition( WEAPONBONUSCONDITION_DRONE_SPOTTING );
  157. //Get my master's AI. If he is attacking something, grant him a range bonus,
  158. //and I'll fly over the target.
  159. Object *target = NULL;
  160. AIUpdateInterface *masterAI = master->getAIUpdateInterface();
  161. if( masterAI )
  162. {
  163. target = masterAI->getCurrentVictim();
  164. }
  165. //Calculate the health percentage of the master -- there are two places we care.
  166. //NOTE: Health percentage will always be 100 should the drone be incapable of
  167. //repairing.
  168. Int healthPercentage = 100;
  169. if( data->m_repairRatePerSecond > 0.0f )
  170. {
  171. BodyModuleInterface *body = master->getBodyModule();
  172. if( body )
  173. {
  174. Real health = body->getHealth();
  175. Real maxHealth = body->getMaxHealth();
  176. healthPercentage = (Int)(health / maxHealth * 100.0f);
  177. }
  178. }
  179. //Determine whether or not we need to go back to the master to repair him
  180. if( healthPercentage <= data->m_repairWhenHealthBelowPercentage )
  181. {
  182. //1ST PRIORITY: Go to the master's position to repair him because he needs it.
  183. doRepairLogic();
  184. return UPDATE_SLEEP_NONE;
  185. }
  186. if( data->m_attackRange )
  187. {
  188. //2ND PRIORITY: Go to the master's current victim (as close as wander distance allows)
  189. if( target )
  190. {
  191. //At this point, we officially are in an attack mode! Now, simply
  192. endRepair();
  193. doAttackLogic( target );
  194. return UPDATE_SLEEP_NONE;
  195. }
  196. }
  197. if( data->m_scoutRange )
  198. {
  199. //3RD PRIORITY: Hover above master's current move destination (as close as wander distance
  200. //allows).
  201. if( masterAI->getPath() )
  202. {
  203. const Coord3D *masterDest = masterAI->getPath()->getLastNode()->getPosition();
  204. //Check to see if master is close to the goal position.
  205. Real distSqr = ThePartitionManager->getDistanceSquared( master, masterDest, FROM_BOUNDINGSPHERE_2D );
  206. if( distSqr > (data->m_guardMaxRange * 0.5f) * (data->m_guardMaxRange * 0.5f) )
  207. {
  208. //If the master's distance to destination is more than half of the guarding range of the slave,
  209. //then order the slave to scout it.
  210. endRepair();
  211. doScoutLogic( masterDest );
  212. return UPDATE_SLEEP_NONE;
  213. }
  214. }
  215. }
  216. //NOTE: Health percentage will always be 100 should the drone be incapable of
  217. //repairing.
  218. if( healthPercentage < 100 )
  219. {
  220. //3RD PRIORITY: Go to the master's position to repair him (because we're idle)
  221. doRepairLogic();
  222. return UPDATE_SLEEP_NONE;
  223. }
  224. // update our "pinned" location based on where our master is
  225. Coord3D pinnedPosition = *master->getPosition();
  226. pinnedPosition.x += m_guardPointOffset.x;
  227. pinnedPosition.y += m_guardPointOffset.y;
  228. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( pinnedPosition.x, pinnedPosition.y );
  229. if( data->m_guardMaxRange )
  230. {
  231. //3RD PRIORITY: Guard the master's area.
  232. if( myAI->isIdle() && ThePartitionManager->getDistanceSquared(me, &pinnedPosition, FROM_CENTER_3D) > CLOSE_ENOUGH_SQR )
  233. {
  234. //I'm idle and too far away.
  235. endRepair();
  236. doGuardLogic( &pinnedPosition );
  237. }
  238. else if( ThePartitionManager->getDistanceSquared( me, master, FROM_CENTER_3D ) > sqr(STRAY_MULTIPLIER * data->m_guardMaxRange ) )
  239. {
  240. //I'm too far away, no matter what I'm doing.
  241. endRepair();
  242. doGuardLogic( &pinnedPosition );
  243. }
  244. }
  245. return UPDATE_SLEEP_NONE;
  246. }
  247. //-------------------------------------------------------------------------------------------------
  248. // We are ordered to attempt to get as close as possible to my master's target.
  249. //-------------------------------------------------------------------------------------------------
  250. void SlavedUpdate::doAttackLogic( const Object *target )
  251. {
  252. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  253. Object *me = getObject();
  254. Object *master = TheGameLogic->findObjectByID( m_slaver );
  255. Coord3D attackPosition;
  256. //First, determine the attack position. If the target is too far away, then we'll
  257. //calculate the closest allowable position.
  258. const Coord3D *targetPos = target->getPosition();
  259. Real dist = ThePartitionManager->getDistanceSquared( me, targetPos, FROM_BOUNDINGSPHERE_2D );
  260. if( dist > sqr( data->m_attackRange ) )
  261. {
  262. //The distance is too far, so calculate the best allowable position.
  263. Coord3D vector;
  264. vector.set( targetPos );
  265. vector.sub( master->getPosition() );
  266. vector.normalize();
  267. vector.scale( data->m_attackRange );
  268. //Now that we have calculated the vector relative to me, add it to my position to get my goal.
  269. attackPosition.set( master->getPosition() );
  270. attackPosition.add( &vector );
  271. }
  272. else
  273. {
  274. //We are close enough, so use the target position -- easy!
  275. attackPosition.set( targetPos );
  276. }
  277. //Finally, if we have a wander distance, then randomly select a point within
  278. //the wander range radius of the pinned position, and we'll go there.
  279. if( data->m_attackWanderRange )
  280. {
  281. //Allow me to wander away from the pinnedPosition.
  282. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  283. m_guardPointOffset.zero();
  284. m_guardPointOffset.x += data->m_attackWanderRange * Cos( randomDirection );
  285. m_guardPointOffset.y += data->m_attackWanderRange * Sin( randomDirection );
  286. //Offset our pinned position by our random offset.
  287. attackPosition.x += m_guardPointOffset.x;
  288. attackPosition.y += m_guardPointOffset.y;
  289. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( attackPosition.x, attackPosition.y );
  290. }
  291. //Move to the updated position!
  292. AIUpdateInterface *ai = me->getAIUpdateInterface();
  293. if( ai )
  294. {
  295. ai->aiMoveToPosition( &attackPosition, CMD_FROM_AI );
  296. }
  297. if( dist < sqr( data->m_distToTargetToGrantRangeBonus ) )
  298. {
  299. //Finally, seeing we are close enough to the target, grant our
  300. //master extended weapon range!
  301. master->setWeaponBonusCondition( WEAPONBONUSCONDITION_DRONE_SPOTTING );
  302. }
  303. }
  304. //-------------------------------------------------------------------------------------------------
  305. // We are ordered to attempt to get as close as possible to my master's movement destination point.
  306. //-------------------------------------------------------------------------------------------------
  307. void SlavedUpdate::doScoutLogic( const Coord3D *mastersDestination )
  308. {
  309. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  310. Object *me = getObject();
  311. Object *master = TheGameLogic->findObjectByID( m_slaver );
  312. Coord3D scoutPosition;
  313. //First, determine the scout position. If our master's destination is too far away, then we'll
  314. //calculate the closest allowable position.
  315. Real dist = ThePartitionManager->getDistanceSquared( me, mastersDestination, FROM_BOUNDINGSPHERE_2D );
  316. if( dist > sqr( data->m_scoutRange ) )
  317. {
  318. //The distance is too far, so calculate the best allowable position.
  319. Coord3D vector;
  320. vector.set( mastersDestination );
  321. vector.sub( master->getPosition() );
  322. vector.normalize();
  323. vector.scale( data->m_scoutRange );
  324. //Now that we have calculated the vector relative to me, add it to my position to get my goal.
  325. scoutPosition.set( master->getPosition() );
  326. scoutPosition.add( &vector );
  327. }
  328. else
  329. {
  330. //We are close enough, so use the target position -- easy!
  331. scoutPosition.set( mastersDestination );
  332. }
  333. //Finally, if we have a wander distance, then randomly select a point within
  334. //the wander range radius of the pinned position, and we'll go there.
  335. if( data->m_scoutWanderRange )
  336. {
  337. //Allow me to wander away from the pinnedPosition.
  338. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  339. m_guardPointOffset.zero();
  340. m_guardPointOffset.x += data->m_scoutWanderRange * Cos( randomDirection );
  341. m_guardPointOffset.y += data->m_scoutWanderRange * Sin( randomDirection );
  342. //Offset our pinned position by our random offset.
  343. scoutPosition.x += m_guardPointOffset.x;
  344. scoutPosition.y += m_guardPointOffset.y;
  345. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( scoutPosition.x, scoutPosition.y );
  346. }
  347. //Move to the updated position!
  348. AIUpdateInterface *ai = me->getAIUpdateInterface();
  349. if( ai )
  350. {
  351. ai->aiMoveToPosition( &scoutPosition, CMD_FROM_AI );
  352. }
  353. }
  354. //-------------------------------------------------------------------------------------------------
  355. // We are ordered to attempt to get as close as possible to my master's position.
  356. //-------------------------------------------------------------------------------------------------
  357. void SlavedUpdate::doGuardLogic( Coord3D *pinnedPosition )
  358. {
  359. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  360. Object *me = getObject();
  361. if( data->m_guardWanderRange )
  362. {
  363. // recalc where we want to be if we wander around
  364. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  365. m_guardPointOffset.zero();
  366. m_guardPointOffset.x += data->m_guardMaxRange * Cos( randomDirection );
  367. m_guardPointOffset.y += data->m_guardMaxRange * Sin( randomDirection );
  368. pinnedPosition->x += m_guardPointOffset.x;
  369. pinnedPosition->y += m_guardPointOffset.y;
  370. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( pinnedPosition->x, pinnedPosition->y );
  371. }
  372. AIUpdateInterface *ai = me->getAIUpdateInterface();
  373. if( ai )
  374. {
  375. ai->aiMoveToPosition( pinnedPosition, CMD_FROM_AI );
  376. }
  377. }
  378. //-------------------------------------------------------------------------------------------------
  379. // We are ordered to repair our master
  380. //-------------------------------------------------------------------------------------------------
  381. void SlavedUpdate::doRepairLogic()
  382. {
  383. Object *me = getObject();
  384. Object *master = TheGameLogic->findObjectByID( m_slaver );
  385. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  386. AIUpdateInterface *ai = me->getAIUpdateInterface();
  387. if( !ai )
  388. {
  389. return;
  390. }
  391. //There are two major things... either move closer or repair.
  392. Real distSqr = ThePartitionManager->getDistanceSquared( me, master, FROM_BOUNDINGSPHERE_2D );
  393. Bool closeEnough = distSqr < 12.0f * 12.0f;
  394. //We're going to do different things based on the repair state.
  395. if( closeEnough )
  396. {
  397. //Now drive the repairing states.
  398. switch( m_repairState )
  399. {
  400. case REPAIRSTATE_NONE:
  401. setRepairState( REPAIRSTATE_READY );
  402. break;
  403. case REPAIRSTATE_READY:
  404. case REPAIRSTATE_EXTENDING:
  405. if( m_framesToWait == 0 )
  406. {
  407. setRepairState( REPAIRSTATE_WELDING );
  408. }
  409. break;
  410. case REPAIRSTATE_UNPACKING:
  411. case REPAIRSTATE_WELDING:
  412. case REPAIRSTATE_RETRACTING:
  413. if( m_framesToWait == 0 )
  414. {
  415. setRepairState( REPAIRSTATE_READY );
  416. }
  417. break;
  418. }
  419. }
  420. else
  421. {
  422. m_repairing = false;
  423. Bool closeEnoughForZPrecision = distSqr < sqr(master->getGeometryInfo().getBoundingSphereRadius() * 2);
  424. //We're too far away to repair, so get closer.
  425. Locomotor *locomotor = ai->getCurLocomotor();
  426. if( locomotor )
  427. {
  428. locomotor->setUsePreciseZPos( closeEnoughForZPrecision );
  429. }
  430. Coord3D pos;
  431. pos.set( master->getPosition() );
  432. Real altitude = GameLogicRandomValueReal( data->m_repairMinAltitude, data->m_repairMaxAltitude );
  433. pos.z += altitude;
  434. ai->aiMoveToPosition( &pos, CMD_FROM_AI );
  435. //Also speed things up by retracting the repair arm (so viewers
  436. //will see it's intentions)
  437. if( m_framesToWait == 0 )
  438. {
  439. setRepairState( REPAIRSTATE_READY );
  440. }
  441. }
  442. if( closeEnough && m_repairing )
  443. {
  444. //We're close enough to repair.
  445. BodyModuleInterface *body = master->getBodyModule();
  446. if( body )
  447. {
  448. //Calculate the repair rate per frame.
  449. Real repairAmount = data->m_repairRatePerSecond / LOGICFRAMES_PER_SECOND;
  450. DamageInfo healingInfo;
  451. healingInfo.in.m_amount = repairAmount;
  452. healingInfo.in.m_damageType = DAMAGE_HEALING;
  453. healingInfo.in.m_deathType = DEATH_NONE;
  454. body->attemptHealing( &healingInfo );
  455. }
  456. }
  457. }
  458. //-------------------------------------------------------------------------------------------------
  459. void SlavedUpdate::endRepair()
  460. {
  461. if( m_repairState != REPAIRSTATE_NONE )
  462. {
  463. //Remove the state so we can start using the update timer again.
  464. m_repairState = REPAIRSTATE_NONE;
  465. m_framesToWait = SLAVED_UPDATE_RATE;
  466. m_repairing = false;
  467. //Set the packing state and clear any other states (to force the arm to fully retract)
  468. setRepairModelConditionStates( MODELCONDITION_PACKING );
  469. }
  470. AIUpdateInterface* ai = getObject()->getAIUpdateInterface();
  471. if( ai )
  472. {
  473. ai->chooseLocomotorSet(LOCOMOTORSET_NORMAL);
  474. Locomotor* locomotor = ai->getCurLocomotor();
  475. if (locomotor)
  476. {
  477. locomotor->setUltraAccurate(false);
  478. locomotor->setUsePreciseZPos(false);
  479. }
  480. }
  481. }
  482. //-------------------------------------------------------------------------------------------------
  483. void SlavedUpdate::setRepairModelConditionStates( ModelConditionFlagType flag )
  484. {
  485. Object *obj = getObject();
  486. obj->clearModelConditionState( MODELCONDITION_PACKING );
  487. obj->clearModelConditionState( MODELCONDITION_UNPACKING );
  488. obj->clearModelConditionState( MODELCONDITION_FIRING_B );
  489. obj->clearModelConditionState( MODELCONDITION_FIRING_C );
  490. obj->clearModelConditionState( MODELCONDITION_BETWEEN_FIRING_SHOTS_B );
  491. obj->clearModelConditionState( MODELCONDITION_BETWEEN_FIRING_SHOTS_C );
  492. obj->clearModelConditionState( MODELCONDITION_RELOADING_B );
  493. obj->clearModelConditionState( MODELCONDITION_RELOADING_C );
  494. obj->setModelConditionState( flag );
  495. }
  496. //-------------------------------------------------------------------------------------------------
  497. void SlavedUpdate::setRepairState( RepairStates repairState )
  498. {
  499. Object *obj = getObject();
  500. Drawable *draw = obj->getDrawable();
  501. //Object *master = TheGameLogic->findObjectByID( m_slaver );
  502. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  503. if( repairState == m_repairState )
  504. {
  505. return;
  506. }
  507. switch( repairState )
  508. {
  509. case REPAIRSTATE_UNPACKING:
  510. setRepairModelConditionStates( MODELCONDITION_UNPACKING );
  511. m_framesToWait = 15;
  512. break;
  513. case REPAIRSTATE_PACKING:
  514. setRepairModelConditionStates( MODELCONDITION_PACKING );
  515. m_framesToWait = 15;
  516. break;
  517. case REPAIRSTATE_READY:
  518. {
  519. switch( m_repairState )
  520. {
  521. case REPAIRSTATE_NONE:
  522. //We're not in a repair state -- so assume we need to unpack
  523. setRepairModelConditionStates( MODELCONDITION_UNPACKING );
  524. m_repairState = REPAIRSTATE_UNPACKING;
  525. m_framesToWait = 15;
  526. break;
  527. case REPAIRSTATE_WELDING:
  528. //We are welding so retract before going into ready state.
  529. m_repairState = REPAIRSTATE_RETRACTING;
  530. m_framesToWait = 5;
  531. setRepairModelConditionStates( MODELCONDITION_FIRING_C );
  532. moveToNewRepairSpot();
  533. break;
  534. default:
  535. m_repairState = REPAIRSTATE_READY;
  536. m_framesToWait = GameLogicRandomValue( data->m_minReadyFrames, data->m_maxReadyFrames );
  537. break;
  538. }
  539. break;
  540. }
  541. case REPAIRSTATE_WELDING:
  542. {
  543. if( m_repairState == REPAIRSTATE_READY )
  544. {
  545. m_repairState = REPAIRSTATE_EXTENDING;
  546. m_framesToWait = 5;
  547. setRepairModelConditionStates( MODELCONDITION_FIRING_B );
  548. break;
  549. }
  550. else
  551. {
  552. m_repairState = REPAIRSTATE_WELDING;
  553. m_framesToWait = GameLogicRandomValue( data->m_minWeldFrames, data->m_maxWeldFrames );
  554. //Make sparks!
  555. if( !data->m_weldingSysName.isEmpty() )
  556. {
  557. const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_weldingSysName );
  558. if( tmp )
  559. {
  560. ParticleSystem *weldingSys = TheParticleSystemManager->createParticleSystem(tmp);
  561. if( weldingSys )
  562. {
  563. Coord3D pos;
  564. //Get the bone position
  565. if( draw->getPristineBonePositions( data->m_weldingFXBone.str(), 0, &pos, NULL, 1 ) )
  566. {
  567. pos.add( obj->getPosition() );
  568. }
  569. else
  570. {
  571. pos.set( obj->getPosition() );
  572. }
  573. weldingSys->setPosition( &pos );
  574. Real time = (Real)(m_framesToWait * LOGICFRAMES_PER_SECOND);
  575. weldingSys->setLifetimeRange( time, time );
  576. AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_repairSparks;
  577. soundToPlay.setPosition( &pos );
  578. TheAudio->addAudioEvent( &soundToPlay );
  579. }
  580. }
  581. }
  582. if( !m_repairing )
  583. {
  584. //Don't actually start healing until we fire our first sparks
  585. m_repairing = true;
  586. AIUpdateInterface *ai = obj->getAIUpdateInterface();
  587. if( ai )
  588. {
  589. //ai->aiFaceObject( master, CMD_FROM_AI );
  590. }
  591. }
  592. break;
  593. }
  594. break;
  595. }
  596. }
  597. }
  598. //-------------------------------------------------------------------------------------------------
  599. void SlavedUpdate::moveToNewRepairSpot()
  600. {
  601. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  602. Object *me = getObject();
  603. Object *master = TheGameLogic->findObjectByID( m_slaver );
  604. //Finally, if we have a wander distance, then randomly select a point within
  605. //the wander range radius of the pinned position, and we'll go there.
  606. if( data->m_repairRange )
  607. {
  608. //Allow me to wander away from the pinnedPosition.
  609. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  610. m_guardPointOffset.set( master->getPosition() );
  611. m_guardPointOffset.x += data->m_repairRange * Cos( randomDirection );
  612. m_guardPointOffset.y += data->m_repairRange * Sin( randomDirection );
  613. m_guardPointOffset.z = TheTerrainLogic->getGroundHeight( m_guardPointOffset.x, m_guardPointOffset.y );
  614. Real altitude = GameLogicRandomValueReal( data->m_repairMinAltitude, data->m_repairMaxAltitude );
  615. m_guardPointOffset.z += altitude;
  616. AIUpdateInterface *ai = me->getAIUpdateInterface();
  617. if( ai )
  618. {
  619. ai->chooseLocomotorSet( LOCOMOTORSET_PANIC );
  620. ai->getCurLocomotor()->setUltraAccurate( true );
  621. ai->aiMoveToPosition( &m_guardPointOffset, CMD_FROM_AI );
  622. Locomotor *locomotor = ai->getCurLocomotor();
  623. if( locomotor )
  624. {
  625. locomotor->setUsePreciseZPos( true );
  626. }
  627. }
  628. }
  629. }
  630. //-------------------------------------------------------------------------------------------------
  631. void SlavedUpdate::startSlavedEffects( const Object *slaver )
  632. {
  633. if( slaver == NULL )
  634. return;
  635. m_slaver = slaver->getID();
  636. const SlavedUpdateModuleData* data = getSlavedUpdateModuleData();
  637. // Decide where our pinned stray point is
  638. Real randomDirection = GameLogicRandomValue( 0, 2*PI );
  639. m_guardPointOffset.zero();
  640. m_guardPointOffset.x += data->m_guardMaxRange * Cos( randomDirection );
  641. m_guardPointOffset.y += data->m_guardMaxRange * Sin( randomDirection );
  642. // mark selves as not selectable
  643. getObject()->setStatus( OBJECT_STATUS_UNSELECTABLE );
  644. }
  645. //-------------------------------------------------------------------------------------------------
  646. void SlavedUpdate::stopSlavedEffects()
  647. {
  648. m_slaver = INVALID_ID;
  649. m_guardPointOffset.zero();
  650. /// @todo Just a thought. Our Status bits on objects really need to be reference counts so you don't clear someone else's flag
  651. getObject()->clearStatus( OBJECT_STATUS_UNSELECTABLE );
  652. getObject()->clearDisabled( DISABLED_HELD );
  653. }
  654. // ------------------------------------------------------------------------------------------------
  655. /** CRC */
  656. // ------------------------------------------------------------------------------------------------
  657. void SlavedUpdate::crc( Xfer *xfer )
  658. {
  659. // extend base class
  660. UpdateModule::crc( xfer );
  661. } // end crc
  662. // ------------------------------------------------------------------------------------------------
  663. /** Xfer method
  664. * Version Info:
  665. * 1: Initial version */
  666. // ------------------------------------------------------------------------------------------------
  667. void SlavedUpdate::xfer( Xfer *xfer )
  668. {
  669. // version
  670. XferVersion currentVersion = 1;
  671. XferVersion version = currentVersion;
  672. xfer->xferVersion( &version, currentVersion );
  673. // extend base class
  674. UpdateModule::xfer( xfer );
  675. // slaver
  676. xfer->xferObjectID( &m_slaver );
  677. // guard point offset
  678. xfer->xferCoord3D( &m_guardPointOffset);
  679. // frames to wait
  680. xfer->xferInt( &m_framesToWait );
  681. // repair state
  682. xfer->xferUser( &m_repairState, sizeof( RepairStates ) );
  683. // repairing
  684. xfer->xferBool( &m_repairing );
  685. } // end xfer
  686. // ------------------------------------------------------------------------------------------------
  687. /** Load post process */
  688. // ------------------------------------------------------------------------------------------------
  689. void SlavedUpdate::loadPostProcess( void )
  690. {
  691. // extend base class
  692. UpdateModule::loadPostProcess();
  693. } // end loadPostProcess