PropagandaTowerBehavior.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  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: PropagandaTowerBehavior.cpp //////////////////////////////////////////////////////////////
  24. // Author: Colin Day, August 2002
  25. // Desc: Behavior module for PropagandaTower
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h"
  29. #include "Common/GameState.h"
  30. #include "Common/Player.h"
  31. #include "Common/PlayerList.h"
  32. #include "Common/Upgrade.h"
  33. #include "Common/Xfer.h"
  34. #include "GameClient/Drawable.h"
  35. #include "GameClient/FXList.h"
  36. #include "GameLogic/GameLogic.h"
  37. #include "GameLogic/Object.h"
  38. #include "GameLogic/PartitionManager.h"
  39. #include "GameLogic/Weapon.h"
  40. #include "GameLogic/Module/PropagandaTowerBehavior.h"
  41. #include "GameLogic/Module/BodyModule.h"
  42. // FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
  43. enum ObjectID;
  44. // ------------------------------------------------------------------------------------------------
  45. /** This class is used to track objects as they exit our area of influence */
  46. // ------------------------------------------------------------------------------------------------
  47. class ObjectTracker : public MemoryPoolObject
  48. {
  49. MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ObjectTracker, "ObjectTracker" );
  50. public:
  51. ObjectTracker( void ) { objectID = INVALID_ID; next = NULL; }
  52. ObjectID objectID;
  53. ObjectTracker *next;
  54. };
  55. ObjectTracker::~ObjectTracker( void ) { }
  56. ///////////////////////////////////////////////////////////////////////////////////////////////////
  57. ///////////////////////////////////////////////////////////////////////////////////////////////////
  58. ///////////////////////////////////////////////////////////////////////////////////////////////////
  59. // ------------------------------------------------------------------------------------------------
  60. // ------------------------------------------------------------------------------------------------
  61. PropagandaTowerBehaviorModuleData::PropagandaTowerBehaviorModuleData( void )
  62. {
  63. m_scanRadius = 1.0f;
  64. m_scanDelayInFrames = 100;
  65. m_autoHealPercentPerSecond = 0.01f;
  66. m_upgradedAutoHealPercentPerSecond = 0.02f;
  67. m_pulseFX = NULL;
  68. m_upgradeRequired = NULL;
  69. m_upgradedPulseFX = NULL;
  70. } // end PropagandaTowerBehaviorModuleData
  71. // ------------------------------------------------------------------------------------------------
  72. // ------------------------------------------------------------------------------------------------
  73. /*static*/ void PropagandaTowerBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
  74. {
  75. UpdateModuleData::buildFieldParse( p );
  76. static const FieldParse dataFieldParse[] =
  77. {
  78. { "Radius", INI::parseReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanRadius ) },
  79. { "DelayBetweenUpdates", INI::parseDurationUnsignedInt, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanDelayInFrames ) },
  80. { "HealPercentEachSecond", INI::parsePercentToReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_autoHealPercentPerSecond ) },
  81. { "UpgradedHealPercentEachSecond", INI::parsePercentToReal,NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedAutoHealPercentPerSecond ) },
  82. { "PulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_pulseFX ) },
  83. { "UpgradeRequired", INI::parseAsciiString, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradeRequired ) },
  84. { "UpgradedPulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedPulseFX ) },
  85. { 0, 0, 0, 0 }
  86. };
  87. p.add( dataFieldParse );
  88. } // end buildFieldParse
  89. ///////////////////////////////////////////////////////////////////////////////////////////////////
  90. ///////////////////////////////////////////////////////////////////////////////////////////////////
  91. ///////////////////////////////////////////////////////////////////////////////////////////////////
  92. // ------------------------------------------------------------------------------------------------
  93. // ------------------------------------------------------------------------------------------------
  94. PropagandaTowerBehavior::PropagandaTowerBehavior( Thing *thing, const ModuleData *modData )
  95. : UpdateModule( thing, modData )
  96. {
  97. //Added By Sadullah Nader
  98. //Initializations inserted
  99. m_lastScanFrame = 0;
  100. //
  101. m_insideList = NULL;
  102. setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
  103. } // end PropagandaTowerBehavior
  104. // ------------------------------------------------------------------------------------------------
  105. // ------------------------------------------------------------------------------------------------
  106. PropagandaTowerBehavior::~PropagandaTowerBehavior( void )
  107. {
  108. } // end ~PropagandaTowerBehavior
  109. // ------------------------------------------------------------------------------------------------
  110. /** Module is being deleted */
  111. // ------------------------------------------------------------------------------------------------
  112. void PropagandaTowerBehavior::onDelete( void )
  113. {
  114. // remove any benefits from anybody in our area of influence
  115. removeAllInfluence();
  116. } // end onDelete
  117. // ------------------------------------------------------------------------------------------------
  118. /** Resolve */
  119. // ------------------------------------------------------------------------------------------------
  120. void PropagandaTowerBehavior::onObjectCreated( void )
  121. {
  122. const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
  123. // convert module upgrade name to a pointer
  124. m_upgradeRequired = TheUpgradeCenter->findUpgrade( modData->m_upgradeRequired );
  125. } // end onObjectCreated
  126. // ------------------------------------------------------------------------------------------------
  127. void PropagandaTowerBehavior::onCapture( Player *oldOwner, Player *newOwner )
  128. {
  129. // We don't function for the neutral player.
  130. if( newOwner == ThePlayerList->getNeutralPlayer() )
  131. {
  132. removeAllInfluence();
  133. setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
  134. }
  135. else
  136. setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
  137. }
  138. // ------------------------------------------------------------------------------------------------
  139. /** The update callback */
  140. // ------------------------------------------------------------------------------------------------
  141. UpdateSleepTime PropagandaTowerBehavior::update( void )
  142. {
  143. /// @todo srj use SLEEPY_UPDATE here
  144. const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
  145. //Sep 27, 2002 (Kris): Added this code to prevent the tower from working while under construction.
  146. Object *self = getObject();
  147. if( BitTest( self->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) )
  148. return UPDATE_SLEEP_NONE;
  149. if( self->testStatus(OBJECT_STATUS_SOLD) )
  150. {
  151. removeAllInfluence();
  152. return UPDATE_SLEEP_FOREVER;
  153. }
  154. if( self->isEffectivelyDead() )
  155. return UPDATE_SLEEP_FOREVER;
  156. if( self->isDisabled() )
  157. {
  158. // We need to let go of everyone if we are EMPd or underpowered or yadda, but not if we are only held
  159. DisabledMaskType allButHeld = MAKE_DISABLED_MASK( DISABLED_HELD );
  160. FLIP_DISABLEDMASK(allButHeld);
  161. if( TEST_DISABLEDMASK_ANY(self->getDisabledFlags(), allButHeld) )
  162. {
  163. removeAllInfluence();
  164. return UPDATE_SLEEP_NONE;
  165. }
  166. }
  167. if( self->getContainedBy() && self->getContainedBy()->getContainedBy() )
  168. {
  169. // If our container is contained, we turn the heck off. Seems like a weird specific check, but all of
  170. // attacking is guarded by the same check in isPassengersAllowedToFire. We similarly work in a container,
  171. // but not in a double container.
  172. removeAllInfluence();
  173. return UPDATE_SLEEP_NONE;
  174. }
  175. // if it's not time to scan, nothing to do
  176. UnsignedInt currentFrame = TheGameLogic->getFrame();
  177. if( currentFrame - m_lastScanFrame >= modData->m_scanDelayInFrames )
  178. {
  179. // do a scan
  180. doScan();
  181. m_lastScanFrame = currentFrame;
  182. } // end if
  183. // go through any objects in our area of influence and do the effect logic on them
  184. Object *obj;
  185. ObjectTracker *curr = NULL, *prev = NULL, *next = NULL;
  186. for( curr = m_insideList; curr; curr = next )
  187. {
  188. // get the next link
  189. next = curr->next;
  190. // find this object
  191. obj = TheGameLogic->findObjectByID( curr->objectID );
  192. if ((obj) &&
  193. (obj->isKindOf(KINDOF_SCORE) || obj->isKindOf(KINDOF_SCORE_CREATE) || obj->isKindOf(KINDOF_SCORE_DESTROY) || obj->isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)))
  194. {
  195. // give any bonus to this object
  196. effectLogic( obj, TRUE, getPropagandaTowerBehaviorModuleData() );
  197. // record this element as the previous one found in the list
  198. prev = curr;
  199. } // end if
  200. else
  201. {
  202. //
  203. // actual object wasn't found, remove this entry from our inside list so we don't
  204. // have to search through it again
  205. //
  206. if( prev )
  207. prev->next = curr->next;
  208. else
  209. m_insideList = curr->next;
  210. curr->deleteInstance();
  211. } // end else
  212. } // end for, curr
  213. return UPDATE_SLEEP_NONE;
  214. } // end update
  215. // ------------------------------------------------------------------------------------------------
  216. /** The death callback */
  217. // ------------------------------------------------------------------------------------------------
  218. void PropagandaTowerBehavior::onDie( const DamageInfo *damageInfo )
  219. {
  220. // remove any benefits from anybody in our area of influence
  221. removeAllInfluence();
  222. } // end onDie
  223. ///////////////////////////////////////////////////////////////////////////////////////////////////
  224. ///////////////////////////////////////////////////////////////////////////////////////////////////
  225. ///////////////////////////////////////////////////////////////////////////////////////////////////
  226. // ------------------------------------------------------------------------------------------------
  227. /** Grant or remove effect to this object */
  228. // ------------------------------------------------------------------------------------------------
  229. void PropagandaTowerBehavior::effectLogic( Object *obj, Bool giving,
  230. const PropagandaTowerBehaviorModuleData *modData )
  231. {
  232. Bool effectUpgraded = getObject()->getControllingPlayer()->hasUpgradeComplete( m_upgradeRequired );
  233. // if giving the effect
  234. if( giving )
  235. {
  236. if ( obj->hasAnyDamageWeapon() == TRUE )
  237. {
  238. if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ) == FALSE )
  239. obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC );
  240. if (effectUpgraded)
  241. {
  242. if (obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ) == FALSE)
  243. obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL );
  244. }
  245. } // hasdamageweapon
  246. // grant health to this object as well
  247. BodyModuleInterface *body = obj->getBodyModule();
  248. if( body )
  249. {
  250. Real healthPercent;
  251. if(effectUpgraded)
  252. healthPercent = modData->m_upgradedAutoHealPercentPerSecond;
  253. else
  254. healthPercent = modData->m_autoHealPercentPerSecond;
  255. Real amount = healthPercent / LOGICFRAMES_PER_SECOND * body->getMaxHealth();
  256. // Dustin wants the healing effect not to stack from multiple propaganda towers...
  257. // To accomplish this, I'll give every object a single healing-sender (ID)
  258. // Any given healing recipient (object) can only receive healing from one particular healing sender
  259. // and cannot change healing senders until the previous one expires (its scandelay)
  260. // obj->attemptHealing(amount, getObject()); // the regular way to give healing...
  261. obj->attemptHealingFromSoleBenefactor( amount, getObject(), modData->m_scanDelayInFrames );//the non-stacking way
  262. } // end if
  263. } // end if
  264. else
  265. {
  266. // taking effect away
  267. obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC );
  268. obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL );
  269. } // end else
  270. } // end effectLogic
  271. // ------------------------------------------------------------------------------------------------
  272. /** Remove all influence from objects we've given bonuses to */
  273. // ------------------------------------------------------------------------------------------------
  274. void PropagandaTowerBehavior::removeAllInfluence( void )
  275. {
  276. ObjectTracker *o;
  277. // go through all objects we've given bonuses to and remove them
  278. Object *obj;
  279. for( o = m_insideList; o; o = o->next )
  280. {
  281. obj = TheGameLogic->findObjectByID( o->objectID );
  282. if( obj )
  283. effectLogic( obj, FALSE, getPropagandaTowerBehaviorModuleData() );
  284. } // end for
  285. // delete the list of objects under our influence
  286. while( m_insideList )
  287. {
  288. o = m_insideList->next;
  289. m_insideList->deleteInstance();
  290. m_insideList = o;
  291. } // end while
  292. } // end removeAllInfluence
  293. // ------------------------------------------------------------------------------------------------
  294. /** Do a scan */
  295. // ------------------------------------------------------------------------------------------------
  296. void PropagandaTowerBehavior::doScan( void )
  297. {
  298. const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
  299. Object *us = getObject();
  300. ObjectTracker *newInsideList = NULL;
  301. // The act of scanning is when we play our effect
  302. Bool upgradePresent = FALSE;
  303. if( m_upgradeRequired )
  304. {
  305. // see if we have the upgrade
  306. switch( m_upgradeRequired->getUpgradeType() )
  307. {
  308. // ------------------------------------------------------------------------------------------
  309. case UPGRADE_TYPE_PLAYER:
  310. {
  311. Player *player = us->getControllingPlayer();
  312. upgradePresent = player->hasUpgradeComplete( m_upgradeRequired );
  313. break;
  314. } // end player upgrade
  315. // ------------------------------------------------------------------------------------------
  316. case UPGRADE_TYPE_OBJECT:
  317. {
  318. upgradePresent = us->hasUpgrade( m_upgradeRequired );
  319. break;
  320. } // end object upgrade
  321. // ------------------------------------------------------------------------------------------
  322. default:
  323. {
  324. DEBUG_CRASH(( "PropagandaTowerBehavior::doScan - Unknown upgrade type '%d'\n",
  325. m_upgradeRequired->getUpgradeType() ));
  326. break;
  327. } // end default
  328. } // end switch
  329. } // end if
  330. // play the right pulse
  331. if( upgradePresent == TRUE )
  332. FXList::doFXObj( modData->m_upgradedPulseFX, us );
  333. else
  334. FXList::doFXObj( modData->m_pulseFX, us );
  335. // setup scan filters
  336. PartitionFilterRelationship relationship( us, PartitionFilterRelationship::ALLOW_ALLIES );
  337. PartitionFilterAlive filterAlive;
  338. PartitionFilterSameMapStatus filterMapStatus(us);
  339. PartitionFilterAcceptByKindOf filterOutBuildings(KINDOFMASK_NONE, MAKE_KINDOF_MASK(KINDOF_STRUCTURE));
  340. PartitionFilter *filters[] = { &relationship,
  341. &filterAlive,
  342. &filterMapStatus,
  343. &filterOutBuildings,
  344. NULL
  345. };
  346. // scan objects in our region
  347. ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( us->getPosition(),
  348. modData->m_scanRadius,
  349. FROM_CENTER_2D,
  350. filters );
  351. MemoryPoolObjectHolder hold( iter );
  352. Object *obj;
  353. ObjectTracker *newEntry;
  354. for( obj = iter->first(); obj; obj = iter->next() )
  355. {
  356. // ignore ourselves, as a tower we're not interesting anyway
  357. if( obj == us )
  358. continue;
  359. // record this object as being in the new "in list"
  360. newEntry = newInstance(ObjectTracker);
  361. newEntry->objectID = obj->getID();
  362. newEntry->next = newInsideList;
  363. newInsideList = newEntry;
  364. } // end for obj
  365. //
  366. // now that we have a list of objects that are in our area of influence, look through
  367. // the objects that were last recorded as in our area of influence and remove any
  368. // bonus we've given them (if they are within the area of effect of another tower it's
  369. // OK, they'll get the bonus back again when that tower does a scan which won't be too long
  370. //
  371. for( ObjectTracker *curr = m_insideList; curr; curr = curr->next )
  372. {
  373. // find this entry in the new list
  374. ObjectTracker *o = NULL;
  375. for( o = newInsideList; o; o = o->next )
  376. if( o->objectID == curr->objectID )
  377. break;
  378. // if entry wasn't there, remove the bonus from this object
  379. if( o == NULL )
  380. {
  381. obj = TheGameLogic->findObjectByID( curr->objectID );
  382. if( obj )
  383. effectLogic( obj, FALSE, modData );
  384. } // end if
  385. } // end for
  386. // delete the inside list we have recoreded
  387. ObjectTracker *next;
  388. while( m_insideList )
  389. {
  390. next = m_insideList->next;
  391. m_insideList->deleteInstance();
  392. m_insideList = next;
  393. } // end while
  394. // set the new inside list to the one we're recording
  395. m_insideList = newInsideList;
  396. } // end doScan
  397. // ------------------------------------------------------------------------------------------------
  398. /** CRC */
  399. // ------------------------------------------------------------------------------------------------
  400. void PropagandaTowerBehavior::crc( Xfer *xfer )
  401. {
  402. // extend base class
  403. UpdateModule::crc( xfer );
  404. } // end crc
  405. // ------------------------------------------------------------------------------------------------
  406. /** Xfer method
  407. * Version Info:
  408. * 1: Initial version */
  409. // ------------------------------------------------------------------------------------------------
  410. void PropagandaTowerBehavior::xfer( Xfer *xfer )
  411. {
  412. // version
  413. XferVersion currentVersion = 1;
  414. XferVersion version = currentVersion;
  415. xfer->xferVersion( &version, currentVersion );
  416. // extend base class
  417. UpdateModule::xfer( xfer );
  418. // last scan frame
  419. xfer->xferUnsignedInt( &m_lastScanFrame );
  420. // inside list tracking
  421. ObjectTracker *trackerEntry;
  422. UnsignedShort insideCount = 0;
  423. for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next )
  424. insideCount++;
  425. xfer->xferUnsignedShort( &insideCount );
  426. if( xfer->getXferMode() == XFER_SAVE )
  427. {
  428. // write all entries
  429. for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next )
  430. {
  431. // object id
  432. xfer->xferObjectID( &trackerEntry->objectID );
  433. } // end for
  434. } // end if, save
  435. else
  436. {
  437. // sanity
  438. if( m_insideList != NULL )
  439. {
  440. DEBUG_CRASH(( "PropagandaTowerBehavior::xfer - m_insideList should be empty but is not\n" ));
  441. throw SC_INVALID_DATA;
  442. } // end if
  443. // read all entries
  444. for( UnsignedShort i = 0; i < insideCount; ++i )
  445. {
  446. // allocate new tracker entry and tie to list
  447. trackerEntry = newInstance(ObjectTracker);
  448. trackerEntry->next = m_insideList;
  449. m_insideList = trackerEntry;
  450. // read object id
  451. xfer->xferObjectID( &trackerEntry->objectID );
  452. } // end for i
  453. } // end else, load
  454. } // end xfer
  455. // ------------------------------------------------------------------------------------------------
  456. /** Load post process */
  457. // ------------------------------------------------------------------------------------------------
  458. void PropagandaTowerBehavior::loadPostProcess( void )
  459. {
  460. // extend base class
  461. UpdateModule::loadPostProcess();
  462. } // end loadPostProcess