BattlePlanUpdate.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  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: BattlePlanUpdate.cpp //////////////////////////////////////////////////////////////////////////
  24. // Author: Kris Morness, September 2002
  25. // Desc: Update module to handle building states and battle plan execution & changes
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  28. #define DEFINE_MAXHEALTHCHANGETYPE_NAMES // for TheMaxHealthChangeTypeNames[]
  29. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  30. #include "Common/BitFlagsIO.h"
  31. #include "Common/Radar.h"
  32. #include "Common/PlayerList.h"
  33. #include "Common/ThingTemplate.h"
  34. #include "Common/ThingFactory.h"
  35. #include "Common/Player.h"
  36. #include "Common/Xfer.h"
  37. #include "GameClient/GameClient.h"
  38. #include "GameClient/Drawable.h"
  39. #include "GameClient/GameText.h"
  40. #include "GameClient/ParticleSys.h"
  41. #include "GameClient/FXList.h"
  42. #include "GameClient/ControlBar.h"
  43. #include "GameLogic/GameLogic.h"
  44. #include "GameLogic/PartitionManager.h"
  45. #include "GameLogic/Object.h"
  46. #include "GameLogic/ObjectIter.h"
  47. #include "GameLogic/Weaponset.h"
  48. #include "GameLogic/Weapon.h"
  49. #include "GameLogic/TerrainLogic.h"
  50. #include "GameLogic/Module/SpecialPowerModule.h"
  51. #include "GameLogic/Module/BattlePlanUpdate.h"
  52. #include "GameLogic/Module/PhysicsUpdate.h"
  53. #include "GameLogic/Module/ActiveBody.h"
  54. #include "GameLogic/Module/AIUpdate.h"
  55. #include "GameLogic/Module/StealthDetectorUpdate.h"
  56. #ifdef _INTERNAL
  57. // for occasional debugging...
  58. //#pragma optimize("", off)
  59. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  60. #endif
  61. //-------------------------------------------------------------------------------------------------
  62. //-------------------------------------------------------------------------------------------------
  63. BattlePlanUpdateModuleData::BattlePlanUpdateModuleData()
  64. {
  65. m_specialPowerTemplate = NULL;
  66. m_bombardmentPlanAnimationFrames = 0;
  67. m_holdTheLinePlanAnimationFrames = 0;
  68. m_searchAndDestroyPlanAnimationFrames = 0;
  69. m_battlePlanParalyzeFrames = 0;
  70. m_holdTheLineArmorDamageScalar = 1.0f;
  71. m_searchAndDestroySightRangeScalar = 1.0f;
  72. m_strategyCenterSearchAndDestroySightRangeScalar = 1.0f;
  73. m_strategyCenterSearchAndDestroyDetectsStealth = true;
  74. m_strategyCenterHoldTheLineMaxHealthScalar = 1.0f;
  75. m_strategyCenterHoldTheLineMaxHealthChangeType = PRESERVE_RATIO;
  76. }
  77. //-------------------------------------------------------------------------------------------------
  78. /*static*/ void BattlePlanUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
  79. {
  80. ModuleData::buildFieldParse(p);
  81. static const FieldParse dataFieldParse[] =
  82. {
  83. { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, NULL, offsetof( BattlePlanUpdateModuleData, m_specialPowerTemplate ) },
  84. { "BombardmentPlanAnimationTime", INI::parseDurationUnsignedInt, NULL, offsetof( BattlePlanUpdateModuleData, m_bombardmentPlanAnimationFrames ) },
  85. { "HoldTheLinePlanAnimationTime", INI::parseDurationUnsignedInt, NULL, offsetof( BattlePlanUpdateModuleData, m_holdTheLinePlanAnimationFrames ) },
  86. { "SearchAndDestroyPlanAnimationTime", INI::parseDurationUnsignedInt, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroyPlanAnimationFrames ) },
  87. { "TransitionIdleTime", INI::parseDurationUnsignedInt, NULL, offsetof( BattlePlanUpdateModuleData, m_transitionIdleFrames ) },
  88. { "BombardmentPlanUnpackSoundName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_bombardmentUnpackName ) },
  89. { "BombardmentPlanPackSoundName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_bombardmentPackName ) },
  90. { "BombardmentMessageLabel", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_bombardmentMessageLabel ) },
  91. { "BombardmentAnnouncementName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_bombardmentAnnouncementName ) },
  92. { "SearchAndDestroyPlanUnpackSoundName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroyUnpackName ) },
  93. { "SearchAndDestroyPlanIdleLoopSoundName",INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroyIdleName ) },
  94. { "SearchAndDestroyPlanPackSoundName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroyPackName ) },
  95. { "SearchAndDestroyMessageLabel", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroyMessageLabel ) },
  96. { "SearchAndDestroyAnnouncementName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroyAnnouncementName ) },
  97. { "HoldTheLinePlanUnpackSoundName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_holdTheLineUnpackName ) },
  98. { "HoldTheLinePlanPackSoundName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_holdTheLinePackName ) },
  99. { "HoldTheLineMessageLabel", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_holdTheLineMessageLabel ) },
  100. { "HoldTheLineAnnouncementName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_holdTheLineAnnouncementName ) },
  101. { "ValidMemberKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( BattlePlanUpdateModuleData, m_validMemberKindOf ) },
  102. { "InvalidMemberKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( BattlePlanUpdateModuleData, m_invalidMemberKindOf ) },
  103. { "BattlePlanChangeParalyzeTime", INI::parseDurationUnsignedInt, NULL, offsetof( BattlePlanUpdateModuleData, m_battlePlanParalyzeFrames ) },
  104. { "HoldTheLinePlanArmorDamageScalar", INI::parseReal, NULL, offsetof( BattlePlanUpdateModuleData, m_holdTheLineArmorDamageScalar ) },
  105. { "SearchAndDestroyPlanSightRangeScalar", INI::parseReal, NULL, offsetof( BattlePlanUpdateModuleData, m_searchAndDestroySightRangeScalar ) },
  106. { "StrategyCenterSearchAndDestroySightRangeScalar", INI::parseReal, NULL, offsetof( BattlePlanUpdateModuleData, m_strategyCenterSearchAndDestroySightRangeScalar ) },
  107. { "StrategyCenterSearchAndDestroyDetectsStealth", INI::parseBool, NULL, offsetof( BattlePlanUpdateModuleData, m_strategyCenterSearchAndDestroyDetectsStealth ) },
  108. { "StrategyCenterHoldTheLineMaxHealthScalar", INI::parseReal, NULL, offsetof( BattlePlanUpdateModuleData, m_strategyCenterHoldTheLineMaxHealthScalar ) },
  109. { "StrategyCenterHoldTheLineMaxHealthChangeType", INI::parseIndexList, TheMaxHealthChangeTypeNames, offsetof( BattlePlanUpdateModuleData, m_strategyCenterHoldTheLineMaxHealthChangeType ) },
  110. { "VisionObjectName", INI::parseAsciiString, NULL, offsetof( BattlePlanUpdateModuleData, m_visionObjectName ) },
  111. { 0, 0, 0, 0 }
  112. };
  113. p.add(dataFieldParse);
  114. }
  115. //-------------------------------------------------------------------------------------------------
  116. BattlePlanUpdate::BattlePlanUpdate( Thing *thing, const ModuleData* moduleData ) :
  117. UpdateModule( thing, moduleData ),
  118. m_bonuses(NULL)
  119. {
  120. const BattlePlanUpdateModuleData *data = getBattlePlanUpdateModuleData();
  121. m_status = TRANSITIONSTATUS_IDLE;
  122. m_currentPlan = PLANSTATUS_NONE;
  123. m_desiredPlan = PLANSTATUS_NONE;
  124. m_planAffectingArmy = PLANSTATUS_NONE;
  125. m_nextReadyFrame = 0;
  126. m_invalidSettings = false;
  127. m_centeringTurret = false;
  128. //Default the bonuses to no change.
  129. m_bonuses = newInstance(BattlePlanBonuses);
  130. m_bonuses->m_armorScalar = 1.0f;
  131. m_bonuses->m_sightRangeScalar = 1.0f;
  132. m_bonuses->m_bombardment = 0;
  133. m_bonuses->m_searchAndDestroy = 0;
  134. m_bonuses->m_holdTheLine = 0;
  135. m_bonuses->m_validKindOf = data->m_validMemberKindOf;
  136. m_bonuses->m_invalidKindOf = data->m_invalidMemberKindOf;
  137. m_visionObjectID = INVALID_ID;
  138. //------------------------//
  139. // Added by Sadullah Nader//
  140. //------------------------//
  141. m_specialPowerModule = NULL;
  142. //
  143. }
  144. //-------------------------------------------------------------------------------------------------
  145. //-------------------------------------------------------------------------------------------------
  146. BattlePlanUpdate::~BattlePlanUpdate( void )
  147. {
  148. TheAudio->removeAudioEvent( m_bombardmentUnpack.getPlayingHandle() );
  149. TheAudio->removeAudioEvent( m_bombardmentPack.getPlayingHandle() );
  150. TheAudio->removeAudioEvent( m_searchAndDestroyUnpack.getPlayingHandle() );
  151. TheAudio->removeAudioEvent( m_searchAndDestroyIdle.getPlayingHandle() );
  152. TheAudio->removeAudioEvent( m_searchAndDestroyPack.getPlayingHandle() );
  153. TheAudio->removeAudioEvent( m_holdTheLineUnpack.getPlayingHandle() );
  154. TheAudio->removeAudioEvent( m_holdTheLinePack.getPlayingHandle() );
  155. }
  156. // ------------------------------------------------------------------------------------------------
  157. /** On delete */
  158. // ------------------------------------------------------------------------------------------------
  159. void BattlePlanUpdate::onDelete()
  160. {
  161. // extend base class
  162. UpdateModule::onDelete();
  163. // delete our vision object, if it exists
  164. Object *obj;
  165. if( m_visionObjectID != INVALID_ID )
  166. {
  167. obj = TheGameLogic->findObjectByID( m_visionObjectID );
  168. if( obj )
  169. TheGameLogic->destroyObject( obj );
  170. } // end if
  171. // If we get destroyed, then make sure we remove our bonus!
  172. // srj sez: we can't do this in the dtor because our team
  173. // (and thus controlling player) has already been nulled by then...
  174. Player* player = getObject()->getControllingPlayer();
  175. // however, player CAN legitimately be null during game reset cycles
  176. // (and which point it doesn't really matter if we can remove the bonus or not)
  177. //DEBUG_ASSERTCRASH(player != NULL, ("Hmm, controller is null"));
  178. if( player && m_planAffectingArmy != PLANSTATUS_NONE )
  179. {
  180. player->changeBattlePlan( m_planAffectingArmy, -1, m_bonuses );
  181. }
  182. }
  183. //-------------------------------------------------------------------------------------------------
  184. // Validate that we have the necessary data from the ini file.
  185. //-------------------------------------------------------------------------------------------------
  186. void BattlePlanUpdate::onObjectCreated()
  187. {
  188. const BattlePlanUpdateModuleData *data = getBattlePlanUpdateModuleData();
  189. Object *obj = getObject();
  190. if( !data->m_specialPowerTemplate )
  191. {
  192. DEBUG_CRASH( ("%s object's BattlePlanUpdate lacks access to the SpecialPowerTemplate. Needs to be specified in ini.", obj->getTemplate()->getName().str() ) );
  193. m_invalidSettings = true;
  194. return;
  195. }
  196. m_specialPowerModule = obj->getSpecialPowerModule( data->m_specialPowerTemplate );
  197. //Create instances of the sounds required.
  198. m_bombardmentUnpack.setEventName( data->m_bombardmentUnpackName );
  199. m_bombardmentPack.setEventName( data->m_bombardmentPackName );
  200. m_bombardmentAnnouncement.setEventName( data->m_bombardmentAnnouncementName );
  201. m_searchAndDestroyUnpack.setEventName( data->m_searchAndDestroyUnpackName );
  202. m_searchAndDestroyIdle.setEventName( data->m_searchAndDestroyIdleName );
  203. m_searchAndDestroyPack.setEventName( data->m_searchAndDestroyPackName );
  204. m_searchAndDestroyAnnouncement.setEventName( data->m_searchAndDestroyAnnouncementName );
  205. m_holdTheLineUnpack.setEventName( data->m_holdTheLineUnpackName );
  206. m_holdTheLinePack.setEventName( data->m_holdTheLinePackName );
  207. m_holdTheLineAnnouncement.setEventName( data->m_holdTheLineAnnouncementName );
  208. TheAudio->getInfoForAudioEvent( &m_bombardmentUnpack );
  209. TheAudio->getInfoForAudioEvent( &m_bombardmentPack );
  210. TheAudio->getInfoForAudioEvent( &m_bombardmentAnnouncement );
  211. TheAudio->getInfoForAudioEvent( &m_searchAndDestroyUnpack );
  212. TheAudio->getInfoForAudioEvent( &m_searchAndDestroyIdle );
  213. TheAudio->getInfoForAudioEvent( &m_searchAndDestroyPack );
  214. TheAudio->getInfoForAudioEvent( &m_searchAndDestroyAnnouncement );
  215. TheAudio->getInfoForAudioEvent( &m_holdTheLineUnpack );
  216. TheAudio->getInfoForAudioEvent( &m_holdTheLinePack );
  217. TheAudio->getInfoForAudioEvent( &m_holdTheLineAnnouncement );
  218. getObject()->setWeaponSetFlag( WEAPONSET_VETERAN );
  219. AIUpdateInterface *ai = obj->getAI();
  220. if( ai )
  221. {
  222. // lock it just till the weapon is empty or the attack is "done"
  223. obj->setWeaponLock( PRIMARY_WEAPON, LOCKED_TEMPORARILY );
  224. }
  225. enableTurret( false );
  226. }
  227. //-------------------------------------------------------------------------------------------------
  228. void BattlePlanUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, UnsignedInt commandOptions, Int locationCount )
  229. {
  230. if( m_specialPowerModule->getSpecialPowerTemplate() != specialPowerTemplate )
  231. {
  232. //Check to make sure our modules are connected.
  233. return;
  234. }
  235. //Set the desired status based on the command button option!
  236. if( BitTest( commandOptions, OPTION_ONE ) )
  237. {
  238. m_desiredPlan = PLANSTATUS_BOMBARDMENT;
  239. }
  240. else if( BitTest( commandOptions, OPTION_TWO ) )
  241. {
  242. m_desiredPlan = PLANSTATUS_HOLDTHELINE;
  243. }
  244. else if( BitTest( commandOptions, OPTION_THREE ) )
  245. {
  246. m_desiredPlan = PLANSTATUS_SEARCHANDDESTROY;
  247. }
  248. }
  249. Bool BattlePlanUpdate::isPowerCurrentlyInUse( const CommandButton *command ) const
  250. {
  251. //@todo -- perhaps we may need this one day...
  252. return false;
  253. }
  254. //-------------------------------------------------------------------------------------------------
  255. CommandOption BattlePlanUpdate::getCommandOption() const
  256. {
  257. switch( m_desiredPlan )
  258. {
  259. case PLANSTATUS_BOMBARDMENT:
  260. return OPTION_ONE;
  261. case PLANSTATUS_HOLDTHELINE:
  262. return OPTION_TWO;
  263. case PLANSTATUS_SEARCHANDDESTROY:
  264. return OPTION_THREE;
  265. }
  266. return (CommandOption)0;
  267. }
  268. //-------------------------------------------------------------------------------------------------
  269. /** The update callback. */
  270. //-------------------------------------------------------------------------------------------------
  271. UpdateSleepTime BattlePlanUpdate::update()
  272. {
  273. if( m_invalidSettings )
  274. {
  275. // can't return UPDATE_SLEEP_FOREVER unless we are sleepy...
  276. return UPDATE_SLEEP_NONE;
  277. ///return UPDATE_SLEEP_FOREVER;
  278. }
  279. //const BattlePlanUpdateModuleData *data = getBattlePlanUpdateModuleData();
  280. //Object *obj = getObject();
  281. UnsignedInt now = TheGameLogic->getFrame();
  282. if( m_nextReadyFrame <= now )
  283. {
  284. switch( m_status )
  285. {
  286. case TRANSITIONSTATUS_IDLE:
  287. //There's only two cases where we are in an idle status -- upon initialization
  288. //when no plan has yet been selected. The other case is after we've finished
  289. //packing the previous plan up and are waiting to unpack the new state.
  290. if( m_desiredPlan != PLANSTATUS_NONE )
  291. {
  292. m_currentPlan = m_desiredPlan;
  293. setStatus( TRANSITIONSTATUS_UNPACKING );
  294. }
  295. break;
  296. case TRANSITIONSTATUS_UNPACKING:
  297. //If we're unpacking, we are forcing the user to wait until the plan is unpacked
  298. //before allowing him to select a new plan. The plan doesn't become active until
  299. //we're finished unpacking.
  300. setStatus( TRANSITIONSTATUS_ACTIVE );
  301. if( m_currentPlan == PLANSTATUS_BOMBARDMENT )
  302. {
  303. enableTurret( true );
  304. }
  305. break;
  306. case TRANSITIONSTATUS_ACTIVE:
  307. //If we're active and the user has selected a different plan, then we need to
  308. //pack up.
  309. if( m_currentPlan != m_desiredPlan )
  310. {
  311. if( m_currentPlan == PLANSTATUS_BOMBARDMENT )
  312. {
  313. //Special case situation -- in bombardment status, we need to center
  314. //the turret prior to packing up, so handle it here.
  315. AIUpdateInterface *ai = getObject()->getAI();
  316. if( ai )
  317. {
  318. if( isTurretInNaturalPosition() )
  319. {
  320. //It's centered, so pack
  321. setStatus( TRANSITIONSTATUS_PACKING );
  322. m_centeringTurret = false;
  323. enableTurret( false );
  324. }
  325. else if( !m_centeringTurret )
  326. {
  327. //It's not centered, and not trying to center, so order it to center.
  328. ai->aiIdle( CMD_FROM_AI );
  329. recenterTurret();
  330. m_centeringTurret = true;
  331. }
  332. }
  333. }
  334. else
  335. {
  336. setStatus( TRANSITIONSTATUS_PACKING );
  337. }
  338. }
  339. break;
  340. case TRANSITIONSTATUS_PACKING:
  341. //If we finished packing, then go idle until we can switch to our new plan.
  342. setStatus( TRANSITIONSTATUS_IDLE );
  343. break;
  344. }
  345. }
  346. return UPDATE_SLEEP_NONE;
  347. }
  348. // ------------------------------------------------------------------------------------------------
  349. /** Create vision objects for all players revealing this building to all */
  350. // ------------------------------------------------------------------------------------------------
  351. void BattlePlanUpdate::createVisionObject()
  352. {
  353. if (m_visionObjectID != INVALID_ID) // don't want two.
  354. return;
  355. const BattlePlanUpdateModuleData *data = getBattlePlanUpdateModuleData();
  356. Object *obj = getObject();
  357. // get template of object to create
  358. const ThingTemplate *tt = TheThingFactory->findTemplate( data->m_visionObjectName );
  359. DEBUG_ASSERTCRASH( tt, ("BattlePlanUpdate::setStatus - Invalid vision object name '%s'\n",
  360. data->m_visionObjectName.str()) );
  361. if (!tt)
  362. return;
  363. Player *pPlayer = ThePlayerList->getNeutralPlayer();
  364. // sanity
  365. if(!pPlayer)
  366. return;
  367. Object *visionObject;
  368. // create object for this player
  369. visionObject = TheThingFactory->newObject( tt, pPlayer->getDefaultTeam() );
  370. if( visionObject )
  371. {
  372. // record we have an object
  373. m_visionObjectID = visionObject->getID();
  374. // set position
  375. visionObject->setPosition( obj->getPosition() );
  376. // set the shroud clearing range
  377. visionObject->setShroudClearingRange( obj->getGeometryInfo().getBoundingSphereRadius() );
  378. } // end if
  379. } // end createVisionObject
  380. //-------------------------------------------------------------------------------------------------
  381. void BattlePlanUpdate::setStatus( TransitionStatus newStatus )
  382. {
  383. const BattlePlanUpdateModuleData *data = getBattlePlanUpdateModuleData();
  384. Object *obj = getObject();
  385. if( m_status == newStatus )
  386. {
  387. return;
  388. }
  389. TransitionStatus oldStatus = m_status;
  390. //Turn off old defining states and sounds
  391. switch( oldStatus )
  392. {
  393. case TRANSITIONSTATUS_IDLE:
  394. break;
  395. case TRANSITIONSTATUS_UNPACKING:
  396. switch( m_currentPlan )
  397. {
  398. case PLANSTATUS_BOMBARDMENT:
  399. obj->clearModelConditionState( MODELCONDITION_DOOR_1_OPENING );
  400. TheAudio->removeAudioEvent( m_bombardmentUnpack.getPlayingHandle() );
  401. break;
  402. case PLANSTATUS_HOLDTHELINE:
  403. obj->clearModelConditionState( MODELCONDITION_DOOR_2_OPENING );
  404. TheAudio->removeAudioEvent( m_holdTheLineUnpack.getPlayingHandle() );
  405. break;
  406. case PLANSTATUS_SEARCHANDDESTROY:
  407. obj->clearModelConditionState( MODELCONDITION_DOOR_3_OPENING );
  408. TheAudio->removeAudioEvent( m_searchAndDestroyUnpack.getPlayingHandle() );
  409. break;
  410. }
  411. break;
  412. case TRANSITIONSTATUS_ACTIVE:
  413. switch( m_currentPlan )
  414. {
  415. case PLANSTATUS_BOMBARDMENT:
  416. obj->clearModelConditionState( MODELCONDITION_DOOR_1_WAITING_TO_CLOSE );
  417. break;
  418. case PLANSTATUS_HOLDTHELINE:
  419. obj->clearModelConditionState( MODELCONDITION_DOOR_2_WAITING_TO_CLOSE );
  420. break;
  421. case PLANSTATUS_SEARCHANDDESTROY:
  422. obj->clearModelConditionState( MODELCONDITION_DOOR_3_WAITING_TO_CLOSE );
  423. TheAudio->removeAudioEvent( m_searchAndDestroyIdle.getPlayingHandle() );
  424. break;
  425. }
  426. break;
  427. case TRANSITIONSTATUS_PACKING:
  428. switch( m_currentPlan )
  429. {
  430. case PLANSTATUS_BOMBARDMENT:
  431. obj->clearModelConditionState( MODELCONDITION_DOOR_1_CLOSING );
  432. TheAudio->removeAudioEvent( m_bombardmentPack.getPlayingHandle() );
  433. break;
  434. case PLANSTATUS_HOLDTHELINE:
  435. obj->clearModelConditionState( MODELCONDITION_DOOR_2_CLOSING );
  436. TheAudio->removeAudioEvent( m_holdTheLinePack.getPlayingHandle() );
  437. break;
  438. case PLANSTATUS_SEARCHANDDESTROY:
  439. obj->clearModelConditionState( MODELCONDITION_DOOR_3_CLOSING );
  440. TheAudio->removeAudioEvent( m_searchAndDestroyPack.getPlayingHandle() );
  441. break;
  442. }
  443. break;
  444. }
  445. UnsignedInt now = TheGameLogic->getFrame();
  446. //Handle entering new status
  447. switch( newStatus )
  448. {
  449. case TRANSITIONSTATUS_IDLE:
  450. {
  451. m_currentPlan = PLANSTATUS_NONE;
  452. m_nextReadyFrame = now + data->m_transitionIdleFrames;
  453. break;
  454. }
  455. case TRANSITIONSTATUS_UNPACKING:
  456. {
  457. // play a radar blip showing the battle plan change in the color of the
  458. TheRadar->createPlayerEvent( obj->getControllingPlayer(),
  459. obj->getPosition(),
  460. RADAR_EVENT_BATTLE_PLAN );
  461. createVisionObject();
  462. switch( m_currentPlan )
  463. {
  464. case PLANSTATUS_BOMBARDMENT:
  465. obj->setModelConditionState( MODELCONDITION_DOOR_1_OPENING );
  466. obj->getDrawable()->setAnimationLoopDuration( data->m_bombardmentPlanAnimationFrames );
  467. m_nextReadyFrame = now + data->m_bombardmentPlanAnimationFrames;
  468. if( m_bombardmentUnpack.getEventName().isNotEmpty() )
  469. {
  470. m_bombardmentUnpack.setObjectID( obj->getID() );
  471. m_bombardmentUnpack.setPlayingHandle( TheAudio->addAudioEvent( &m_bombardmentUnpack ) );
  472. }
  473. // display a message to *all* users
  474. TheInGameUI->message( TheGameText->fetch( data->m_bombardmentMessageLabel ) );
  475. if( m_bombardmentAnnouncement.getEventName().isEmpty() == FALSE )
  476. {
  477. m_bombardmentAnnouncement.setPosition( obj->getPosition() );
  478. TheAudio->addAudioEvent( &m_bombardmentAnnouncement );
  479. }
  480. break;
  481. case PLANSTATUS_HOLDTHELINE:
  482. obj->setModelConditionState( MODELCONDITION_DOOR_2_OPENING );
  483. obj->getDrawable()->setAnimationLoopDuration( data->m_holdTheLinePlanAnimationFrames );
  484. m_nextReadyFrame = now + data->m_holdTheLinePlanAnimationFrames;
  485. if( m_holdTheLineUnpack.getEventName().isNotEmpty() )
  486. {
  487. m_holdTheLineUnpack.setObjectID( obj->getID() );
  488. m_holdTheLineUnpack.setPlayingHandle( TheAudio->addAudioEvent( &m_holdTheLineUnpack ) );
  489. }
  490. // display a message to *all* users
  491. TheInGameUI->message( TheGameText->fetch( data->m_holdTheLineMessageLabel ) );
  492. if( m_holdTheLineAnnouncement.getEventName().isEmpty() == FALSE )
  493. {
  494. m_holdTheLineAnnouncement.setPosition( obj->getPosition() );
  495. TheAudio->addAudioEvent( &m_holdTheLineAnnouncement );
  496. }
  497. break;
  498. case PLANSTATUS_SEARCHANDDESTROY:
  499. obj->setModelConditionState( MODELCONDITION_DOOR_3_OPENING );
  500. obj->getDrawable()->setAnimationLoopDuration( data->m_searchAndDestroyPlanAnimationFrames );
  501. m_nextReadyFrame = now + data->m_searchAndDestroyPlanAnimationFrames;
  502. if( m_searchAndDestroyUnpack.getEventName().isNotEmpty() )
  503. {
  504. m_searchAndDestroyUnpack.setObjectID( obj->getID() );
  505. m_searchAndDestroyUnpack.setPlayingHandle( TheAudio->addAudioEvent( &m_searchAndDestroyUnpack ) );
  506. }
  507. // display a message to *all* users
  508. TheInGameUI->message( TheGameText->fetch( data->m_searchAndDestroyMessageLabel ) );
  509. if( m_searchAndDestroyAnnouncement.getEventName().isEmpty() == FALSE )
  510. {
  511. m_searchAndDestroyAnnouncement.setPosition( obj->getPosition() );
  512. TheAudio->addAudioEvent( &m_searchAndDestroyAnnouncement );
  513. }
  514. break;
  515. }
  516. break;
  517. }
  518. case TRANSITIONSTATUS_ACTIVE:
  519. setBattlePlan( m_currentPlan );
  520. switch( m_currentPlan )
  521. {
  522. case PLANSTATUS_BOMBARDMENT:
  523. obj->setModelConditionState( MODELCONDITION_DOOR_1_WAITING_TO_CLOSE );
  524. break;
  525. case PLANSTATUS_HOLDTHELINE:
  526. obj->setModelConditionState( MODELCONDITION_DOOR_2_WAITING_TO_CLOSE );
  527. break;
  528. case PLANSTATUS_SEARCHANDDESTROY:
  529. obj->setModelConditionState( MODELCONDITION_DOOR_3_WAITING_TO_CLOSE );
  530. if( m_searchAndDestroyIdle.getEventName().isNotEmpty() )
  531. {
  532. m_searchAndDestroyIdle.setObjectID( obj->getID() );
  533. m_searchAndDestroyIdle.setPlayingHandle( TheAudio->addAudioEvent( &m_searchAndDestroyIdle ) );
  534. }
  535. break;
  536. }
  537. break;
  538. case TRANSITIONSTATUS_PACKING:
  539. setBattlePlan( PLANSTATUS_NONE );
  540. switch( m_currentPlan )
  541. {
  542. case PLANSTATUS_BOMBARDMENT:
  543. obj->setModelConditionState( MODELCONDITION_DOOR_1_CLOSING );
  544. obj->getDrawable()->setAnimationLoopDuration( data->m_bombardmentPlanAnimationFrames );
  545. m_nextReadyFrame = now + data->m_bombardmentPlanAnimationFrames;
  546. if( m_bombardmentUnpack.getEventName().isNotEmpty() )
  547. {
  548. m_bombardmentPack.setObjectID( obj->getID() );
  549. m_bombardmentPack.setPlayingHandle( TheAudio->addAudioEvent( &m_bombardmentPack ) );
  550. }
  551. break;
  552. case PLANSTATUS_HOLDTHELINE:
  553. obj->setModelConditionState( MODELCONDITION_DOOR_2_CLOSING );
  554. obj->getDrawable()->setAnimationLoopDuration( data->m_holdTheLinePlanAnimationFrames );
  555. m_nextReadyFrame = now + data->m_holdTheLinePlanAnimationFrames;
  556. if( m_holdTheLineUnpack.getEventName().isNotEmpty() )
  557. {
  558. m_holdTheLinePack.setObjectID( obj->getID() );
  559. m_holdTheLinePack.setPlayingHandle( TheAudio->addAudioEvent( &m_holdTheLinePack ) );
  560. }
  561. break;
  562. case PLANSTATUS_SEARCHANDDESTROY:
  563. obj->setModelConditionState( MODELCONDITION_DOOR_3_CLOSING );
  564. obj->getDrawable()->setAnimationLoopDuration( data->m_searchAndDestroyPlanAnimationFrames );
  565. m_nextReadyFrame = now + data->m_searchAndDestroyPlanAnimationFrames;
  566. if( m_searchAndDestroyUnpack.getEventName().isNotEmpty() )
  567. {
  568. m_searchAndDestroyPack.setObjectID( obj->getID() );
  569. m_searchAndDestroyPack.setPlayingHandle( TheAudio->addAudioEvent( &m_searchAndDestroyPack ) );
  570. }
  571. break;
  572. }
  573. break;
  574. }
  575. //Set new status.
  576. m_status = newStatus;
  577. }
  578. //------------------------------------------------------------------------------------------------
  579. void BattlePlanUpdate::enableTurret( Bool enable )
  580. {
  581. AIUpdateInterface *ai = getObject()->getAI();
  582. if( ai )
  583. {
  584. WhichTurretType tur = ai->getWhichTurretForCurWeapon();
  585. if( tur != TURRET_INVALID )
  586. {
  587. ai->setTurretEnabled( tur, enable );
  588. }
  589. }
  590. }
  591. //------------------------------------------------------------------------------------------------
  592. void BattlePlanUpdate::recenterTurret()
  593. {
  594. AIUpdateInterface *ai = getObject()->getAI();
  595. if( ai )
  596. {
  597. WhichTurretType tur = ai->getWhichTurretForCurWeapon();
  598. if( tur != TURRET_INVALID )
  599. {
  600. ai->recenterTurret( tur );
  601. }
  602. }
  603. }
  604. //------------------------------------------------------------------------------------------------
  605. Bool BattlePlanUpdate::isTurretInNaturalPosition()
  606. {
  607. AIUpdateInterface *ai = getObject()->getAI();
  608. if( ai )
  609. {
  610. WhichTurretType tur = ai->getWhichTurretForCurWeapon();
  611. if( tur != TURRET_INVALID )
  612. {
  613. return ai->isTurretInNaturalPosition( tur );
  614. }
  615. }
  616. return false;
  617. }
  618. //------------------------------------------------------------------------------------------------
  619. static void paralyzeTroop( Object *obj, void *userData )
  620. {
  621. const BattlePlanUpdateModuleData *data = (BattlePlanUpdateModuleData*)userData;
  622. if( obj->isAnyKindOf( data->m_validMemberKindOf ) )
  623. {
  624. if( !obj->isAnyKindOf( data->m_invalidMemberKindOf ) )
  625. {
  626. obj->setDisabledUntil( DISABLED_PARALYZED, TheGameLogic->getFrame() + data->m_battlePlanParalyzeFrames );
  627. }
  628. }
  629. }
  630. //------------------------------------------------------------------------------------------------
  631. void BattlePlanUpdate::setBattlePlan( BattlePlanStatus plan )
  632. {
  633. const BattlePlanUpdateModuleData *data = getBattlePlanUpdateModuleData();
  634. Object *obj = getObject();
  635. Player *player = obj->getControllingPlayer();
  636. if( player )
  637. {
  638. switch( m_planAffectingArmy )
  639. {
  640. case PLANSTATUS_BOMBARDMENT:
  641. {
  642. //Remove the previous plan!
  643. player->changeBattlePlan( PLANSTATUS_BOMBARDMENT, -1, m_bonuses );
  644. //The only building bonus is actually the turret, and that's already handled!
  645. break;
  646. }
  647. case PLANSTATUS_HOLDTHELINE:
  648. {
  649. //Remove the previous plan!
  650. player->changeBattlePlan( PLANSTATUS_HOLDTHELINE, -1, m_bonuses );
  651. //Remove building health bonuses
  652. if( data->m_strategyCenterHoldTheLineMaxHealthScalar != 1.0f )
  653. {
  654. BodyModuleInterface *body = obj->getBodyModule();
  655. body->setMaxHealth( body->getMaxHealth() * 1.0f / data->m_strategyCenterHoldTheLineMaxHealthScalar, data->m_strategyCenterHoldTheLineMaxHealthChangeType );
  656. }
  657. break;
  658. }
  659. case PLANSTATUS_SEARCHANDDESTROY:
  660. {
  661. //Remove the previous plan!
  662. player->changeBattlePlan( PLANSTATUS_SEARCHANDDESTROY, -1, m_bonuses );
  663. //Remove sight range bonus
  664. if( data->m_strategyCenterSearchAndDestroySightRangeScalar != 1.0f )
  665. {
  666. obj->setVisionRange( obj->getVisionRange() * 1.0f / data->m_strategyCenterSearchAndDestroySightRangeScalar );
  667. obj->setShroudClearingRange( obj->getShroudClearingRange() * 1.0f / data->m_strategyCenterSearchAndDestroySightRangeScalar );
  668. }
  669. //Remove stealth detection
  670. if( data->m_strategyCenterSearchAndDestroyDetectsStealth )
  671. {
  672. static NameKeyType key_StealthDetectorUpdate = NAMEKEY( "StealthDetectorUpdate" );
  673. StealthDetectorUpdate *update = (StealthDetectorUpdate*)obj->findUpdateModule( key_StealthDetectorUpdate );
  674. if( update )
  675. {
  676. update->setSDEnabled( false );
  677. }
  678. }
  679. break;
  680. }
  681. }
  682. //Revert to default no-bonuses!
  683. m_bonuses->m_armorScalar = 1.0f;
  684. m_bonuses->m_sightRangeScalar = 1.0f;
  685. m_bonuses->m_bombardment = 0;
  686. m_bonuses->m_searchAndDestroy = 0;
  687. m_bonuses->m_holdTheLine = 0;
  688. //Add new bonuses!
  689. switch( plan )
  690. {
  691. case PLANSTATUS_NONE:
  692. //Paralyze troops!
  693. player->iterateObjects( paralyzeTroop, (void*)data );
  694. break;
  695. case PLANSTATUS_BOMBARDMENT:
  696. //Set the bombardment bonuses
  697. m_bonuses->m_bombardment = 1; //for weapon bonuses
  698. //Add the new plan!
  699. player->changeBattlePlan( PLANSTATUS_BOMBARDMENT, 1, m_bonuses );
  700. break;
  701. case PLANSTATUS_HOLDTHELINE:
  702. //Add building health bonuses
  703. if( data->m_strategyCenterHoldTheLineMaxHealthScalar )
  704. {
  705. BodyModuleInterface *body = obj->getBodyModule();
  706. body->setMaxHealth( body->getMaxHealth() * data->m_strategyCenterHoldTheLineMaxHealthScalar, data->m_strategyCenterHoldTheLineMaxHealthChangeType );
  707. }
  708. //Set the hold-the-line bonuses
  709. m_bonuses->m_armorScalar = data->m_holdTheLineArmorDamageScalar;
  710. m_bonuses->m_holdTheLine = 1; //for weapon bonuses
  711. //Add the new plan!
  712. player->changeBattlePlan( PLANSTATUS_HOLDTHELINE, 1, m_bonuses );
  713. break;
  714. case PLANSTATUS_SEARCHANDDESTROY:
  715. //Add sight range bonus
  716. if( data->m_strategyCenterSearchAndDestroySightRangeScalar != 1.0f )
  717. {
  718. obj->setVisionRange( obj->getVisionRange() * data->m_strategyCenterSearchAndDestroySightRangeScalar );
  719. obj->setShroudClearingRange( obj->getShroudClearingRange() * data->m_strategyCenterSearchAndDestroySightRangeScalar );
  720. }
  721. //Enable stealth detection
  722. if( data->m_strategyCenterSearchAndDestroyDetectsStealth )
  723. {
  724. static NameKeyType key_StealthDetectorUpdate = NAMEKEY( "StealthDetectorUpdate" );
  725. StealthDetectorUpdate *update = (StealthDetectorUpdate*)obj->findUpdateModule( key_StealthDetectorUpdate );
  726. if( update )
  727. {
  728. update->setSDEnabled( true );
  729. }
  730. }
  731. //Set the search-and-destroy bonuses
  732. m_bonuses->m_searchAndDestroy = 1; //for weapon bonuses
  733. m_bonuses->m_sightRangeScalar = data->m_searchAndDestroySightRangeScalar;
  734. //Add the new plan!
  735. player->changeBattlePlan( PLANSTATUS_SEARCHANDDESTROY, 1, m_bonuses );
  736. break;
  737. }
  738. m_planAffectingArmy = plan;
  739. }
  740. }
  741. //------------------------------------------------------------------------------------------------
  742. //Returns the currently active battle plan -- unpacked and ready... returns PLANSTATUS_NONE if in
  743. //transition!
  744. //------------------------------------------------------------------------------------------------
  745. BattlePlanStatus BattlePlanUpdate::getActiveBattlePlan() const
  746. {
  747. if( m_status == TRANSITIONSTATUS_ACTIVE )
  748. {
  749. return m_planAffectingArmy;
  750. }
  751. return PLANSTATUS_NONE;
  752. }
  753. //------------------------------------------------------------------------------------------------
  754. void BattlePlanUpdate::crc( Xfer *xfer )
  755. {
  756. // extend base class
  757. UpdateModule::crc( xfer );
  758. } // end crc
  759. //------------------------------------------------------------------------------------------------
  760. // Xfer method
  761. // Version Info:
  762. // 1: Initial version
  763. //------------------------------------------------------------------------------------------------
  764. void BattlePlanUpdate::xfer( Xfer *xfer )
  765. {
  766. // version
  767. XferVersion currentVersion = 1;
  768. XferVersion version = currentVersion;
  769. xfer->xferVersion( &version, currentVersion );
  770. // extend base class
  771. UpdateModule::xfer( xfer );
  772. // current plan
  773. xfer->xferUser( &m_currentPlan, sizeof( BattlePlanStatus ) );
  774. // desired plan
  775. xfer->xferUser( &m_desiredPlan, sizeof( BattlePlanStatus ) );
  776. // plan affecting army
  777. xfer->xferUser( &m_planAffectingArmy, sizeof( BattlePlanStatus ) );
  778. // status
  779. xfer->xferUser( &m_status, sizeof( TransitionStatus ) );
  780. // next ready frame
  781. xfer->xferUnsignedInt( &m_nextReadyFrame );
  782. // don't need to save this interface, it's retrived on object creation
  783. // SpecialPowerModuleInterface *m_specialPowerModule;
  784. // invalid settings
  785. xfer->xferBool( &m_invalidSettings );
  786. // centering turret
  787. xfer->xferBool( &m_centeringTurret );
  788. // bonuses
  789. xfer->xferReal( &m_bonuses->m_armorScalar );
  790. xfer->xferInt( &m_bonuses->m_bombardment );
  791. xfer->xferInt( &m_bonuses->m_searchAndDestroy );
  792. xfer->xferInt( &m_bonuses->m_holdTheLine );
  793. xfer->xferReal( &m_bonuses->m_sightRangeScalar );
  794. m_bonuses->m_validKindOf.xfer( xfer );
  795. m_bonuses->m_invalidKindOf.xfer( xfer );
  796. // vision object data
  797. xfer->xferObjectID( &m_visionObjectID );
  798. } // end xfer
  799. //------------------------------------------------------------------------------------------------
  800. void BattlePlanUpdate::loadPostProcess( void )
  801. {
  802. // extend base class
  803. UpdateModule::loadPostProcess();
  804. } // end loadPostProcess