StealthUpdate.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  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: StealthUpdate.cpp ////////////////////////////////////////////////////////////////////////
  24. // Author: Kris Morness, May 2002
  25. // Desc: An update that checks for a status bit to stealth the owning object
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  28. #define DEFINE_STEALTHLEVEL_NAMES
  29. #define DEFINE_OBJECT_STATUS_NAMES
  30. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  31. #include "Common/GameState.h"
  32. #include "Common/Player.h"
  33. #include "Common/PlayerList.h"
  34. #include "Common/Radar.h"
  35. #include "Common/Team.h"
  36. #include "Common/ThingTemplate.h"
  37. #include "Common/ThingFactory.h"
  38. #include "Common/Xfer.h"
  39. #include "GameClient/ControlBar.h"
  40. #include "GameClient/Drawable.h"
  41. #include "GameClient/FXList.h"
  42. #include "GameClient/GameClient.h"
  43. #include "GameLogic/Damage.h"
  44. #include "GameLogic/Object.h"
  45. #include "GameLogic/PartitionManager.h"
  46. #include "GameLogic/Weapon.h"
  47. #include "GameLogic/Module/AIUpdate.h"
  48. #include "GameLogic/Module/StealthUpdate.h"
  49. #include "GameLogic/Module/PhysicsUpdate.h"
  50. #include "GameLogic/Module/ContainModule.h"
  51. #ifdef _INTERNAL
  52. // for occasional debugging...
  53. //#pragma optimize("", off)
  54. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  55. #endif
  56. //-------------------------------------------------------------------------------------------------
  57. void StealthUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
  58. {
  59. UpdateModuleData::buildFieldParse(p);
  60. static const FieldParse dataFieldParse[] =
  61. {
  62. { "StealthDelay", INI::parseDurationUnsignedInt, NULL, offsetof( StealthUpdateModuleData, m_stealthDelay ) },
  63. { "MoveThresholdSpeed", INI::parseVelocityReal, NULL, offsetof( StealthUpdateModuleData, m_stealthSpeed ) },
  64. { "StealthForbiddenConditions", INI::parseBitString32, TheStealthLevelNames, offsetof( StealthUpdateModuleData, m_stealthLevel) },
  65. { "HintDetectableConditions", INI::parseBitString32, TheObjectStatusBitNames, offsetof( StealthUpdateModuleData, m_hintDetectableStates) },
  66. { "FriendlyOpacityMin", INI::parsePercentToReal, NULL, offsetof( StealthUpdateModuleData, m_friendlyOpacityMin ) },
  67. { "FriendlyOpacityMax", INI::parsePercentToReal, NULL, offsetof( StealthUpdateModuleData, m_friendlyOpacityMax ) },
  68. { "PulseFrequency", INI::parseDurationUnsignedInt, NULL, offsetof( StealthUpdateModuleData, m_pulseFrames ) },
  69. { "DisguisesAsTeam", INI::parseBool, NULL, offsetof( StealthUpdateModuleData, m_teamDisguised ) },
  70. { "RevealDistanceFromTarget", INI::parseReal, NULL, offsetof( StealthUpdateModuleData, m_revealDistanceFromTarget ) },
  71. { "OrderIdleEnemiesToAttackMeUponReveal", INI::parseBool, NULL, offsetof( StealthUpdateModuleData, m_orderIdleEnemiesToAttackMeUponReveal ) },
  72. { "DisguiseFX", INI::parseFXList, NULL, offsetof( StealthUpdateModuleData, m_disguiseFX ) },
  73. { "DisguiseRevealFX", INI::parseFXList, NULL, offsetof( StealthUpdateModuleData, m_disguiseRevealFX ) },
  74. { "DisguiseTransitionTime", INI::parseDurationUnsignedInt, NULL, offsetof( StealthUpdateModuleData, m_disguiseTransitionFrames ) },
  75. { "DisguiseRevealTransitionTime", INI::parseDurationUnsignedInt, NULL, offsetof( StealthUpdateModuleData, m_disguiseRevealTransitionFrames ) },
  76. { "InnateStealth", INI::parseBool, NULL, offsetof( StealthUpdateModuleData, m_innateStealth ) },
  77. { 0, 0, 0, 0 }
  78. };
  79. p.add(dataFieldParse);
  80. }
  81. //-------------------------------------------------------------------------------------------------
  82. //-------------------------------------------------------------------------------------------------
  83. StealthUpdate::StealthUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
  84. {
  85. const StealthUpdateModuleData *data = getStealthUpdateModuleData();
  86. m_stealthAllowedFrame = TheGameLogic->getFrame() + data->m_stealthDelay;
  87. //Must be enabled manually if using disguise system (bomb truck uses)
  88. m_enabled = !data->m_teamDisguised;
  89. //Added By Sadullah Nader
  90. //Initialization(s) inserted
  91. m_detectionExpiresFrame = 0;
  92. //
  93. m_pulsePhaseRate = 0.2f;
  94. m_pulsePhase = GameClientRandomValueReal(0, PI);
  95. m_disguiseAsPlayerIndex = -1;
  96. m_disguiseAsTemplate = NULL;
  97. m_transitioningToDisguise = false;
  98. m_disguised = false;
  99. m_disguiseTransitionFrames = 0;
  100. m_disguiseHalfpointReached = false;
  101. if( data->m_innateStealth )
  102. {
  103. //Giving innate stealth units this status bit allows other code to easily check the status bit.
  104. getObject()->setStatus( OBJECT_STATUS_CAN_STEALTH );
  105. }
  106. // start active, since some stealths start enabled from the get-go
  107. setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
  108. // we do not need to restore a disguise
  109. m_xferRestoreDisguise = FALSE;
  110. }
  111. //-------------------------------------------------------------------------------------------------
  112. //-------------------------------------------------------------------------------------------------
  113. StealthUpdate::~StealthUpdate( void )
  114. {
  115. }
  116. //-------------------------------------------------------------------------------------------------
  117. Bool StealthUpdate::allowedToStealth() const
  118. {
  119. const Object *self = getObject();
  120. UnsignedInt flags = getStealthUpdateModuleData()->m_stealthLevel;
  121. if( flags & STEALTH_NOT_WHILE_ATTACKING && self->getStatusBits() & OBJECT_STATUS_IS_FIRING_WEAPON )
  122. {
  123. //Doesn't stealth while aggressive (includes approaching).
  124. return false;
  125. }
  126. if( flags & STEALTH_NOT_WHILE_USING_ABILITY && self->getStatusBits() & OBJECT_STATUS_IS_USING_ABILITY )
  127. {
  128. //Doesn't stealth while using a special ability (starting with preparation, which takes place after unpacking).
  129. return false;
  130. }
  131. //Do a quick preliminary test to see if we are restricted by firing particular weapons and we fired a shot last frame or this frame.
  132. if( flags & STEALTH_NOT_WHILE_FIRING_WEAPON && self->getStatusBits() & OBJECT_STATUS_IS_FIRING_WEAPON )
  133. {
  134. if( (flags & STEALTH_NOT_WHILE_FIRING_WEAPON) == STEALTH_NOT_WHILE_FIRING_WEAPON )
  135. {
  136. //Not allowed to stealth while firing ANY weapon!
  137. return false;
  138. }
  139. //Now do weapon specific checks.
  140. Weapon *weapon;
  141. UnsignedInt lastFrame = TheGameLogic->getFrame() - 1;
  142. if( flags & STEALTH_NOT_WHILE_FIRING_PRIMARY )
  143. {
  144. //Check primary weapon status
  145. weapon = self->getWeaponInWeaponSlot( PRIMARY_WEAPON );
  146. if( weapon && weapon->getLastShotFrame() >= lastFrame )
  147. {
  148. return false;
  149. }
  150. }
  151. if( flags & STEALTH_NOT_WHILE_FIRING_SECONDARY )
  152. {
  153. //Check secondary weapon status
  154. weapon = self->getWeaponInWeaponSlot( SECONDARY_WEAPON );
  155. if( weapon && weapon->getLastShotFrame() >= lastFrame )
  156. {
  157. return false;
  158. }
  159. }
  160. if( flags & STEALTH_NOT_WHILE_FIRING_TERTIARY )
  161. {
  162. //Check tertiary weapon status
  163. weapon = self->getWeaponInWeaponSlot( TERTIARY_WEAPON );
  164. if( weapon && weapon->getLastShotFrame() >= lastFrame )
  165. {
  166. return false;
  167. }
  168. }
  169. }
  170. const PhysicsBehavior *physics = self->getPhysics();
  171. if ((flags & STEALTH_NOT_WHILE_MOVING) && physics != NULL &&
  172. physics->getVelocityMagnitude() > getStealthUpdateModuleData()->m_stealthSpeed)
  173. return false;
  174. if( self->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNSTEALTHED))
  175. {
  176. //We can't stealth because a script disabled this ability for this object!
  177. return false;
  178. }
  179. return true;
  180. }
  181. //---------------------------------------------------------------------------
  182. //-------------------------------------------------------------------------------------------------
  183. void StealthUpdate::hintDetectableWhileUnstealthed()
  184. {
  185. Object *self = getObject();
  186. const StealthUpdateModuleData *md = getStealthUpdateModuleData();
  187. if( self && (md->m_hintDetectableStates & self->getStatusBits()) )
  188. {
  189. if ( self->getControllingPlayer() == ThePlayerList->getLocalPlayer() )
  190. {
  191. Drawable *selfDraw = self->getDrawable();
  192. if ( selfDraw )
  193. selfDraw->setHeatVisionOpacity( 1.0f );
  194. }
  195. }
  196. }
  197. //-------------------------------------------------------------------------------
  198. Real StealthUpdate::getFriendlyOpacity() const
  199. {
  200. return getStealthUpdateModuleData()->m_friendlyOpacityMin;
  201. }
  202. //=============================================================================
  203. // indicate how the given unit is "stealthed" with respect to a given player.
  204. StealthLookType StealthUpdate::calcStealthedStatusForPlayer(const Object* obj, const Player* player)
  205. {
  206. /*
  207. for stealthy things, there are these distinct "logical" states:
  208. -- not stealthed at all (ie, totally visible)
  209. -- stealthed
  210. -- stealthed-but-detected
  211. and the following visual states:
  212. -- normal (n)
  213. -- invisible (i)
  214. -- stealthed-but-visible-to-friendly-folks (sv)
  215. -- stealthed-but-visible-to-everyone-due-to-being-detected (sd)
  216. Let's be ubergeeks and make a matrix of the possibilities:
  217. Ally Nonally
  218. normal: (n) (n)
  219. stealthed: (sv) (i)
  220. detected: (sd) (sd)
  221. Or, to put it another way:
  222. If normal, you always appear normal.
  223. If stealthed (and not detected), you appear as (sv) to allies and (i) to others.
  224. If detected, you always appears as (sd).
  225. Sorry, there is one more condition, stealthed, but visible to friendly folks, YET detected
  226. In this state we render outselves visible and we ovlerlay the detection effect as a warning
  227. we'll call this STEALTHLOOK_VISIBLE_FRIENDLY_DETECTED
  228. */
  229. if (obj->isEffectivelyDead())
  230. return STEALTHLOOK_NONE; // making sure he turns visible when he dies
  231. if (obj->getStatusBits() & OBJECT_STATUS_STEALTHED)
  232. {
  233. const Team* team = obj->getTeam();
  234. Relationship r = team ? team->getRelationship(player->getDefaultTeam()) : NEUTRAL;
  235. if( !player->isPlayerActive() )
  236. {
  237. //Observer players are friends to everyone!
  238. r = ALLIES;
  239. }
  240. // srj sez: disguised stuff doesn't work well when combined with the normal "detected" stuff.
  241. // so special case it here.
  242. if (canDisguise())
  243. {
  244. if (r != ALLIES && isDisguised())
  245. return STEALTHLOOK_DISGUISED_ENEMY;
  246. else
  247. return STEALTHLOOK_NONE;
  248. }
  249. if (obj->getStatusBits() & OBJECT_STATUS_DETECTED) // we're detected.
  250. {
  251. if (r == ALLIES)// if we're friendly to the given player, detection DOES matter though.
  252. return STEALTHLOOK_VISIBLE_FRIENDLY_DETECTED;
  253. else
  254. return STEALTHLOOK_VISIBLE_DETECTED;
  255. }
  256. else
  257. {
  258. if (r == ALLIES)
  259. {
  260. // if we're friendly to the given player, detection doesn't matter.
  261. return STEALTHLOOK_VISIBLE_FRIENDLY;
  262. }
  263. else
  264. {
  265. // srj sez: disguised stuff doesn't work well when combined with the normal "detected" stuff.
  266. // so special case it above.
  267. // if( getStealthUpdateModuleData()->m_teamDisguised )
  268. // {
  269. // return STEALTHLOOK_DISGUISED_ENEMY;
  270. // }
  271. // we're effectively hidden.
  272. return STEALTHLOOK_INVISIBLE;
  273. }
  274. }
  275. }
  276. else
  277. {
  278. return STEALTHLOOK_NONE;
  279. }
  280. }
  281. //-------------------------------------------------------------------------------------------------
  282. //-------------------------------------------------------------------------------------------------
  283. UpdateSleepTime StealthUpdate::calcSleepTime() const
  284. {
  285. return m_enabled ? UPDATE_SLEEP_NONE : UPDATE_SLEEP_FOREVER;
  286. }
  287. //-------------------------------------------------------------------------------------------------
  288. /** The update callback. */
  289. //-------------------------------------------------------------------------------------------------
  290. UpdateSleepTime StealthUpdate::update( void )
  291. {
  292. // restore disguise if we need to from a game load
  293. if( m_xferRestoreDisguise == TRUE )
  294. {
  295. Drawable *draw = getObject()->getDrawable();
  296. Bool wasHidden = FALSE;
  297. // hack! if drawable was hidden (such as if we're inside a container) we must keep that state
  298. if( draw && draw->isDrawableEffectivelyHidden() )
  299. wasHidden = TRUE;
  300. // do the change (we get a new drawable from this)
  301. changeVisualDisguise();
  302. // restore hidden state in the new drawable
  303. draw = getObject()->getDrawable();
  304. if( wasHidden && draw )
  305. draw->setDrawableHidden( TRUE );
  306. } // end if
  307. Object *self = getObject();
  308. UnsignedInt now = TheGameLogic->getFrame();
  309. const StealthUpdateModuleData *data = getStealthUpdateModuleData();
  310. /// @todo srj -- improve sleeping behavior. we currently just sleep when not enabled,
  311. // and demand every-frame attention when enabled. this could probably be smartened.
  312. if( !m_enabled )
  313. {
  314. return calcSleepTime();
  315. }
  316. Drawable* draw = self->getDrawable();
  317. if (draw)
  318. {
  319. //Are we disguise transitioning (either gaining or losing disguise look?)
  320. /** @todo srj -- evil hack here... this whole heat-vision thing is fucked.
  321. don't want it on mines but no good way to do that. hack for now. */
  322. if (self->isKindOf(KINDOF_MINE))
  323. {
  324. // special case for mines
  325. draw->setEffectiveOpacity( 0.0f, 0.0f );
  326. }
  327. else if( m_disguiseTransitionFrames )
  328. {
  329. m_disguiseTransitionFrames--;
  330. Real factor;
  331. if( m_transitioningToDisguise )
  332. {
  333. factor = 1.0f - ( (Real)m_disguiseTransitionFrames / (Real)data->m_disguiseTransitionFrames );
  334. }
  335. else
  336. {
  337. factor = 1.0f - ( (Real)m_disguiseTransitionFrames / (Real)data->m_disguiseRevealTransitionFrames );
  338. }
  339. if( factor >= 0.5f && !m_disguiseHalfpointReached )
  340. {
  341. //Switch models at the halfway point
  342. changeVisualDisguise();
  343. m_disguiseHalfpointReached = true;
  344. }
  345. //Opacity ranges from full to none at midpoint and full again at the end
  346. Real opacity = fabs( 1.0f - (factor * 2.0f) );
  347. Real overrideOpacity = opacity < 1.0f ? 0.0f : 1.0f;
  348. draw->setEffectiveOpacity( opacity, overrideOpacity );
  349. if( !m_disguiseTransitionFrames && !m_transitioningToDisguise )
  350. {
  351. //We're finished removing disguise so turn off stealth update.
  352. m_enabled = false;
  353. self->clearStatus( OBJECT_STATUS_STEALTHED );
  354. self->clearStatus( OBJECT_STATUS_DETECTED );
  355. return calcSleepTime();
  356. }
  357. }
  358. else
  359. {
  360. draw->setEffectiveOpacity( 0.5f + ( Sin( m_pulsePhase ) * 0.5f ) );
  361. // between one half and full opacity
  362. m_pulsePhase += m_pulsePhaseRate;
  363. }
  364. }
  365. /// @todo srj -- do we need to do this EVERY frame?
  366. Real revealDistance = getRevealDistanceFromTarget();
  367. if( revealDistance > 0.0f )
  368. {
  369. AIUpdateInterface *ai = self->getAI();
  370. if( ai )
  371. {
  372. Object *target = ai->getCurrentVictim();
  373. if( target )
  374. {
  375. Real distSqrd = ThePartitionManager->getDistanceSquared( self, target, FROM_CENTER_2D );
  376. if( distSqrd <= revealDistance * revealDistance )
  377. {
  378. //We're close enough to reveal ourselves
  379. markAsDetected();
  380. return calcSleepTime();
  381. }
  382. }
  383. }
  384. }
  385. // If the object is unable to Stealth, don't bother trying.
  386. if( !(self->getStatusBits() & OBJECT_STATUS_CAN_STEALTH) )
  387. {
  388. return calcSleepTime();
  389. }
  390. if (allowedToStealth())
  391. {
  392. // If I can stealth, don't attempt to Stealth until the timer is zero.
  393. if( m_stealthAllowedFrame > now )
  394. {
  395. return calcSleepTime();
  396. }
  397. // If we haven't stealthed yet( still destealthed ), play stealthOn here
  398. //if ( ( self->getStatusBits() && OBJECT_STATUS_STEALTHED ) == 0 )
  399. if ( ( self->getStatusBits() & OBJECT_STATUS_STEALTHED ) == 0 )
  400. {
  401. AudioEventRTS soundEvent = *self->getTemplate()->getSoundStealthOn();
  402. soundEvent.setObjectID(self->getID());
  403. TheAudio->addAudioEvent( &soundEvent );
  404. }
  405. // The timer is zero, so if we aren't stealthed, do so now!
  406. self->setStatus( OBJECT_STATUS_STEALTHED );
  407. }
  408. else
  409. {
  410. m_stealthAllowedFrame = now + getStealthUpdateModuleData()->m_stealthDelay;
  411. // if you are destealthing on your own free will, play sound for all to hear
  412. if ( ( self->getStatusBits() & OBJECT_STATUS_STEALTHED ) != 0 )
  413. {
  414. AudioEventRTS soundEvent = *self->getTemplate()->getSoundStealthOn();
  415. soundEvent.setObjectID(self->getID());
  416. TheAudio->addAudioEvent( &soundEvent );
  417. }
  418. self->clearStatus( OBJECT_STATUS_STEALTHED );
  419. hintDetectableWhileUnstealthed();
  420. }
  421. Bool detectedStatusChangedThisFrame = FALSE;
  422. if (m_detectionExpiresFrame > now)
  423. {
  424. // if this is the first time being detected, play stealth off sound
  425. if( !(self->getStatusBits() & OBJECT_STATUS_DETECTED) )
  426. {
  427. detectedStatusChangedThisFrame = TRUE;
  428. AudioEventRTS soundEvent = *self->getTemplate()->getSoundStealthOff();
  429. soundEvent.setObjectID(self->getID());
  430. TheAudio->addAudioEvent( &soundEvent );
  431. }
  432. self->setStatus( OBJECT_STATUS_DETECTED );
  433. }
  434. else
  435. {
  436. // if this is the first time your clearing the detected status, play the stealth on sound
  437. if( ( self->getStatusBits() & OBJECT_STATUS_DETECTED ) )
  438. {
  439. detectedStatusChangedThisFrame = TRUE;
  440. //Only play sound effect if the selected object is controllable.
  441. if( self->isLocallyControlled() )
  442. {
  443. AudioEventRTS soundEvent = *self->getTemplate()->getSoundStealthOn();
  444. soundEvent.setObjectID(self->getID());
  445. TheAudio->addAudioEvent( &soundEvent );
  446. }
  447. }
  448. self->clearStatus( OBJECT_STATUS_DETECTED );
  449. }
  450. if ( detectedStatusChangedThisFrame )
  451. {
  452. //do the trick where we tell our container to recals his apparent controlling player
  453. //since I may have just become either detected or undetected
  454. if ( self->isContained() )
  455. {
  456. Object *container = self->getContainedBy();
  457. if ( container )
  458. {
  459. ContainModuleInterface *contain = container->getContain();
  460. if( contain && contain->isGarrisonable() )
  461. {
  462. contain->recalcApparentControllingPlayer();
  463. }
  464. }
  465. }
  466. }
  467. if (draw)
  468. {
  469. StealthLookType stealthLook = calcStealthedStatusForPlayer( self, ThePlayerList->getLocalPlayer() );
  470. draw->setStealthLook( stealthLook );
  471. }
  472. return calcSleepTime();
  473. }
  474. //-------------------------------------------------------------------------------------------------
  475. void setWakeupIfInRange( Object *obj, void *userData)
  476. {
  477. Object *victim = (Object *)userData;
  478. AIUpdateInterface *ai = obj->getAI();
  479. if (!ai) {
  480. return;
  481. }
  482. Real vision = obj->getVisionRange();
  483. Coord3D srcpos = *obj->getPosition();
  484. Coord3D dstpos = *victim->getPosition();
  485. srcpos.sub(&dstpos);
  486. if (srcpos.length() > vision)
  487. return;
  488. ai->wakeUpAndAttemptToTarget();
  489. // if( obj->isKindOf( KINDOF_SELECTABLE ) && ( obj->isAbleToAttack() || !obj->isKindOf( KINDOF_STRUCTURE ) )) {
  490. // Drawable *draw = obj->getDrawable();
  491. // if( draw ) {
  492. // draw->setEmoticon( "Emoticon_Alarm", 5000 );
  493. // }
  494. // }
  495. }
  496. //-------------------------------------------------------------------------------------------------
  497. void StealthUpdate::markAsDetected(UnsignedInt numFrames)
  498. {
  499. Object *self = getObject();
  500. const StealthUpdateModuleData *data = getStealthUpdateModuleData();
  501. Player *thisPlayer = self->getControllingPlayer();
  502. //If we are disguised, remove the disguise permanently!
  503. if( isDisguised() )
  504. {
  505. disguiseAsObject( NULL );
  506. }
  507. UnsignedInt now = TheGameLogic->getFrame();
  508. if( !numFrames )
  509. {
  510. //Kris:
  511. //If numFrames is zero (the default value), use the stealth delay specified in the ini file.
  512. m_detectionExpiresFrame = now + data->m_stealthDelay;
  513. }
  514. else if ( m_detectionExpiresFrame < now + numFrames )
  515. {
  516. m_detectionExpiresFrame = now + numFrames;
  517. }
  518. if( data->m_orderIdleEnemiesToAttackMeUponReveal )
  519. {
  520. // This can't be a partitionmanager thing, because we need to know which objects can see
  521. // us. Therefore, walk the play list, and for each player that considers us an enemy,
  522. // check if any of their units can see us.
  523. Int numPlayers = ThePlayerList->getPlayerCount();
  524. for (Int n = 0; n < numPlayers; ++n)
  525. {
  526. Player *player = ThePlayerList->getNthPlayer(n);
  527. if (!player)
  528. continue;
  529. if (player->getRelationship(thisPlayer->getDefaultTeam()) != ENEMIES)
  530. continue;
  531. player->iterateObjects(setWakeupIfInRange, self);
  532. }
  533. }
  534. }
  535. //-------------------------------------------------------------------------------------------------
  536. void StealthUpdate::disguiseAsObject( const Object *target )
  537. {
  538. Object *self = getObject();
  539. const StealthUpdateModuleData *data = getStealthUpdateModuleData();
  540. if( target && target->getControllingPlayer() )
  541. {
  542. static NameKeyType key_StealthUpdate = NAMEKEY( "StealthUpdate" );
  543. StealthUpdate* stealth = (StealthUpdate*)target->findUpdateModule( key_StealthUpdate );
  544. if( stealth && stealth->getDisguisedTemplate() )
  545. {
  546. m_disguiseAsTemplate = stealth->getDisguisedTemplate();
  547. m_disguiseAsPlayerIndex = stealth->getDisguisedPlayerIndex();
  548. }
  549. else
  550. {
  551. m_disguiseAsTemplate = target->getTemplate();
  552. m_disguiseAsPlayerIndex = target->getControllingPlayer()->getPlayerIndex();
  553. }
  554. m_enabled = true;
  555. m_transitioningToDisguise = true; //Means we are gaining disguise over time.
  556. m_disguiseTransitionFrames = data->m_disguiseTransitionFrames;
  557. m_disguiseHalfpointReached = false;
  558. //Wake up so I can process!
  559. setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
  560. }
  561. else if( m_disguised )
  562. {
  563. m_disguiseAsTemplate = NULL;
  564. m_disguiseAsPlayerIndex = 0;
  565. m_disguiseTransitionFrames = data->m_disguiseRevealTransitionFrames;
  566. m_transitioningToDisguise = false; //Means we are losing the disguise over time.
  567. m_disguiseHalfpointReached = false;
  568. }
  569. Drawable *draw = self->getDrawable();
  570. if( draw && draw->isSelected() )
  571. {
  572. TheControlBar->markUIDirty();
  573. }
  574. }
  575. //-------------------------------------------------------------------------------------------------
  576. void StealthUpdate::changeVisualDisguise()
  577. {
  578. Object *self = getObject();
  579. const StealthUpdateModuleData *data = getStealthUpdateModuleData();
  580. Drawable *draw = self->getDrawable();
  581. // We need to maintain our selection across the un/disguise, so pull selected out here.
  582. Bool selected = draw->isSelected();
  583. if( m_disguiseAsTemplate )
  584. {
  585. Player *player = ThePlayerList->getNthPlayer( m_disguiseAsPlayerIndex );
  586. ModelConditionFlags flags = draw->getModelConditionFlags();
  587. //Get rid of the old instance!
  588. TheGameClient->destroyDrawable( draw );
  589. draw = TheThingFactory->newDrawable( m_disguiseAsTemplate );
  590. if( draw )
  591. {
  592. TheGameLogic->bindObjectAndDrawable(self, draw);
  593. draw->setPosition( self->getPosition() );
  594. draw->setOrientation( self->getOrientation() );
  595. draw->setModelConditionFlags( flags );
  596. draw->updateDrawable();
  597. self->getPhysics()->resetDynamicPhysics();
  598. if( selected )
  599. {
  600. TheInGameUI->selectDrawable( draw );
  601. }
  602. Player *clientPlayer = ThePlayerList->getLocalPlayer();
  603. if( self->getControllingPlayer()->getRelationship( clientPlayer->getDefaultTeam() ) != ALLIES && clientPlayer->isPlayerActive() )
  604. {
  605. //Neutrals and enemies will see this disguised unit as the team it's disguised as.
  606. if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
  607. draw->setIndicatorColor( player->getPlayerNightColor() );
  608. else
  609. draw->setIndicatorColor( player->getPlayerColor() );
  610. }
  611. else
  612. {
  613. //If it's on our team or our ally's team, then show it's true colors.
  614. if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
  615. draw->setIndicatorColor( self->getNightIndicatorColor() );
  616. else
  617. draw->setIndicatorColor( self->getIndicatorColor() );
  618. }
  619. }
  620. //Play a disguise sound!
  621. AudioEventRTS sound = *self->getTemplate()->getPerUnitSound( "DisguiseStarted" );
  622. sound.setObjectID( self->getID() );
  623. TheAudio->addAudioEvent( &sound );
  624. FXList::doFXPos( data->m_disguiseFX, self->getPosition() );
  625. m_disguised = true;
  626. }
  627. else if( m_disguiseAsPlayerIndex != -1 )
  628. {
  629. m_disguiseAsPlayerIndex = -1;
  630. ModelConditionFlags flags = draw->getModelConditionFlags();
  631. //Get rid of the old instance!
  632. TheGameClient->destroyDrawable( draw );
  633. const ThingTemplate *tTemplate = self->getTemplate();
  634. TheThingFactory->newDrawable( tTemplate );
  635. if( draw )
  636. {
  637. TheGameLogic->bindObjectAndDrawable(self, draw);
  638. draw->setPosition( self->getPosition() );
  639. draw->setOrientation( self->getOrientation() );
  640. draw->setModelConditionFlags( flags );
  641. draw->updateDrawable();
  642. self->getPhysics()->resetDynamicPhysics();
  643. if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
  644. draw->setIndicatorColor( self->getNightIndicatorColor() );
  645. else
  646. draw->setIndicatorColor( self->getIndicatorColor() );
  647. if( selected )
  648. {
  649. TheInGameUI->selectDrawable( draw );
  650. }
  651. //UGH!
  652. //A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been
  653. //made. When the bomb truck disguises as something else, these subobjects are lost because the vector is
  654. //stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to
  655. //recalculate those upgraded subobjects.
  656. self->forceRefreshSubObjectUpgradeStatus();
  657. }
  658. Bool successfulReveal = false;
  659. AIUpdateInterface *ai = self->getAI();
  660. if( ai )
  661. {
  662. Object *currTarget = ai->getCurrentVictim();
  663. if( currTarget )
  664. {
  665. successfulReveal = true;
  666. }
  667. }
  668. //Play a reveal sound!
  669. AudioEventRTS sound;
  670. if( successfulReveal )
  671. {
  672. sound = *self->getTemplate()->getPerUnitSound( "DisguiseRevealedSuccess" );
  673. }
  674. else
  675. {
  676. sound = *self->getTemplate()->getPerUnitSound( "DisguiseRevealedFailure" );
  677. }
  678. sound.setObjectID( self->getID() );
  679. TheAudio->addAudioEvent( &sound );
  680. FXList::doFXPos( data->m_disguiseRevealFX, self->getPosition() );
  681. m_disguised = false;
  682. }
  683. //Reset the radar (determines color on add)
  684. TheRadar->removeObject( self );
  685. TheRadar->addObject( self );
  686. // couldn't possibly need to restore a disguise now :)
  687. m_xferRestoreDisguise = FALSE;
  688. }
  689. // ------------------------------------------------------------------------------------------------
  690. /** CRC */
  691. // ------------------------------------------------------------------------------------------------
  692. void StealthUpdate::crc( Xfer *xfer )
  693. {
  694. // extend base class
  695. UpdateModule::crc( xfer );
  696. } // end crc
  697. // ------------------------------------------------------------------------------------------------
  698. /** Xfer method
  699. * Version Info:
  700. * 1: Initial version */
  701. // ------------------------------------------------------------------------------------------------
  702. void StealthUpdate::xfer( Xfer *xfer )
  703. {
  704. // version
  705. XferVersion currentVersion = 1;
  706. XferVersion version = currentVersion;
  707. xfer->xferVersion( &version, currentVersion );
  708. // extend base class
  709. UpdateModule::xfer( xfer );
  710. // stealth allowed frame
  711. xfer->xferUnsignedInt( &m_stealthAllowedFrame );
  712. // detection expires frame
  713. xfer->xferUnsignedInt( &m_detectionExpiresFrame );
  714. // enabled
  715. xfer->xferBool( &m_enabled );
  716. // pulse phase rate
  717. xfer->xferReal( &m_pulsePhaseRate );
  718. // pulse phase
  719. xfer->xferReal( &m_pulsePhase );
  720. // disguise as player index
  721. xfer->xferInt( &m_disguiseAsPlayerIndex );
  722. // disguise as template
  723. AsciiString name = m_disguiseAsTemplate ? m_disguiseAsTemplate->getName() : AsciiString::TheEmptyString;
  724. xfer->xferAsciiString( &name );
  725. if( xfer->getXferMode() == XFER_LOAD )
  726. {
  727. m_disguiseAsTemplate = NULL;
  728. if( name.isEmpty() == FALSE )
  729. {
  730. m_disguiseAsTemplate = TheThingFactory->findTemplate( name );
  731. if( m_disguiseAsTemplate == NULL )
  732. {
  733. DEBUG_CRASH(( "StealthUpdate::xfer - Unknown template '%s'\n", name.str() ));
  734. throw SC_INVALID_DATA;
  735. } // end if
  736. } // end if
  737. } // end if
  738. // disguise transition frames
  739. xfer->xferUnsignedInt( &m_disguiseTransitionFrames );
  740. // disguise halfpoint reached
  741. xfer->xferBool( &m_disguiseHalfpointReached );
  742. // transitioning to disguise
  743. xfer->xferBool( &m_transitioningToDisguise );
  744. // disguised
  745. xfer->xferBool( &m_disguised );
  746. } // end xfer
  747. // ------------------------------------------------------------------------------------------------
  748. /** Load post process */
  749. // ------------------------------------------------------------------------------------------------
  750. void StealthUpdate::loadPostProcess( void )
  751. {
  752. // extend base class
  753. UpdateModule::loadPostProcess();
  754. //
  755. // we will need to restore our disguise when the game is ready to run ... NOTE that we
  756. // cannot restore it here because if we called changeVisualDisguise() it would
  757. // destroy our drawable and create new stuff. The destruction of a drawable during
  758. // a load is *very* bad ... it has a snapshot instance in the game state, other things
  759. // may be pointing at it etc.
  760. //
  761. if( isDisguised() )
  762. m_xferRestoreDisguise = TRUE;
  763. } // end loadPostProcess