CleanupHazardUpdate.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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: CleanupHazardUpdate.cpp //////////////////////////////////////////////////////////////////////////
  24. // Author: Kris Morness, August 2002
  25. // Desc: Update module to handle independent targeting of hazards to cleanup.
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
  29. #define DEFINE_WEAPONSLOTTYPE_NAMES
  30. #include "Common\RandomValue.h"
  31. #include "Common\ThingTemplate.h"
  32. #include "Common\Xfer.h"
  33. #include "GameClient\Drawable.h"
  34. #include "GameLogic\GameLogic.h"
  35. #include "GameLogic\PartitionManager.h"
  36. #include "GameLogic\Object.h"
  37. #include "GameLogic\ObjectIter.h"
  38. #include "GameLogic\Module\CleanupHazardUpdate.h"
  39. #include "GameLogic\Module\PhysicsUpdate.h"
  40. #include "GameLogic\Weapon.h"
  41. #include "GameLogic\WeaponSet.h"
  42. #include "GameLogic\Module\AIUpdate.h"
  43. #ifdef _INTERNAL
  44. // for occasional debugging...
  45. //#pragma optimize("", off)
  46. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  47. #endif
  48. //-------------------------------------------------------------------------------------------------
  49. //-------------------------------------------------------------------------------------------------
  50. CleanupHazardUpdateModuleData::CleanupHazardUpdateModuleData()
  51. {
  52. m_weaponSlot = PRIMARY_WEAPON;
  53. m_scanFrames = 0;
  54. m_scanRange = 0.0f;
  55. }
  56. //-------------------------------------------------------------------------------------------------
  57. /*static*/ void CleanupHazardUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
  58. {
  59. ModuleData::buildFieldParse(p);
  60. static const FieldParse dataFieldParse[] =
  61. {
  62. { "WeaponSlot", INI::parseLookupList, TheWeaponSlotTypeNamesLookupList, offsetof( CleanupHazardUpdateModuleData, m_weaponSlot ) },
  63. { "ScanRate", INI::parseDurationUnsignedInt, NULL, offsetof( CleanupHazardUpdateModuleData, m_scanFrames ) },
  64. { "ScanRange", INI::parseReal, NULL, offsetof( CleanupHazardUpdateModuleData, m_scanRange ) },
  65. { 0, 0, 0, 0 }
  66. };
  67. p.add(dataFieldParse);
  68. }
  69. //-------------------------------------------------------------------------------------------------
  70. CleanupHazardUpdate::CleanupHazardUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
  71. {
  72. m_bestTargetID = INVALID_ID;
  73. m_nextScanFrames = 0;
  74. m_nextShotAvailableInFrames = 0;
  75. m_inRange = false;
  76. m_weaponTemplate = NULL;
  77. m_moveRange = 0.0f;
  78. m_pos.zero();
  79. }
  80. //-------------------------------------------------------------------------------------------------
  81. //-------------------------------------------------------------------------------------------------
  82. CleanupHazardUpdate::~CleanupHazardUpdate( void )
  83. {
  84. }
  85. //-------------------------------------------------------------------------------------------------
  86. void CleanupHazardUpdate::onObjectCreated()
  87. {
  88. const CleanupHazardUpdateModuleData *data = getCleanupHazardUpdateModuleData();
  89. Object *self = getObject();
  90. //Make sure we have a weapon template
  91. self->setWeaponSetFlag( WEAPONSET_VETERAN );
  92. Weapon *weapon = self->getWeaponInWeaponSlot( data->m_weaponSlot );
  93. if( !weapon )
  94. {
  95. DEBUG_CRASH( ("CleanupHazardUpdate for %s doesn't have a valid weapon template",
  96. getObject()->getTemplate()->getName().str() ) );
  97. return;
  98. }
  99. m_weaponTemplate = weapon->getTemplate();
  100. //Make sure our firing range is smaller than the scan range.
  101. WeaponBonus bonus;
  102. bonus.clear();
  103. Real attackRange = m_weaponTemplate->getAttackRange( bonus );
  104. if( data->m_scanRange <= attackRange )
  105. {
  106. DEBUG_CRASH( ("CleanupHazardUpdate for %s requires the scan range (%.1f) being larger than the firing range (%.1f)",
  107. getObject()->getTemplate()->getName().str(), data->m_scanRange, attackRange ) );
  108. }
  109. }
  110. //-------------------------------------------------------------------------------------------------
  111. /** The update callback. */
  112. //-------------------------------------------------------------------------------------------------
  113. UpdateSleepTime CleanupHazardUpdate::update()
  114. {
  115. /// @todo srj use SLEEPY_UPDATE here
  116. const CleanupHazardUpdateModuleData *data = getCleanupHazardUpdateModuleData();
  117. Object *obj = getObject();
  118. //Make sure we are "busy" for scripting purposes if the unit is cleaning up an area.
  119. if( m_moveRange > 0.0f )
  120. {
  121. //Means we are cleaning up an AREA, not just things immediately in range.
  122. AIUpdateInterface *ai = obj->getAI();
  123. if( ai )
  124. {
  125. if( ai->isIdle() )
  126. {
  127. //Keep him busy even though he's not moving -- he might be cleaning up.
  128. ai->aiBusy( CMD_FROM_AI );
  129. }
  130. else if( ai->getLastCommandSource() != CMD_FROM_AI )
  131. {
  132. //Either the player or a script gave a NEW order so abandon the cleanup area cause.
  133. m_moveRange = 0.0f;
  134. return UPDATE_SLEEP_NONE;
  135. }
  136. }
  137. }
  138. //Optimized firing at acquired target
  139. if( m_nextScanFrames > 0 )
  140. {
  141. m_nextScanFrames--;
  142. fireWhenReady(); //Only happens if something is tracked.
  143. return UPDATE_SLEEP_NONE;
  144. }
  145. m_nextScanFrames = data->m_scanFrames;
  146. //Periodic scanning (expensive)
  147. if( scanClosestTarget() )
  148. {
  149. //1 frame can make a big difference so fire ASAP!
  150. fireWhenReady();
  151. }
  152. else if( m_moveRange )
  153. {
  154. //There's nothing nearby, so if we are cleaning up an area versus hazards
  155. //immediately in range, set the AI to idle so it can advance to the next script!
  156. AIUpdateInterface *ai = obj->getAI();
  157. if( ai && (ai->isIdle() || ai->isBusy()) )
  158. {
  159. Real fDist = sqrt( ThePartitionManager->getDistanceSquared( obj, &m_pos, FROM_CENTER_2D ) );
  160. if( fDist < 25.0f )
  161. {
  162. //Abort clean area because there's nothing left to clean!
  163. m_moveRange = 0.0f;
  164. }
  165. else
  166. {
  167. //Abort clean area AFTER we move back to our initial position!
  168. ai->aiMoveToPosition( &m_pos, CMD_FROM_AI );
  169. }
  170. }
  171. }
  172. return UPDATE_SLEEP_NONE;
  173. }
  174. //-------------------------------------------------------------------------------------------------
  175. void CleanupHazardUpdate::fireWhenReady()
  176. {
  177. const CleanupHazardUpdateModuleData *data = getCleanupHazardUpdateModuleData();
  178. Object *self = getObject();
  179. //Track our target (this code prevents the object from moving beyond range)
  180. //If we are ordered to clean an area, then range doesn't matter because
  181. //we allow the unit to move to the area.
  182. Object *target = TheGameLogic->findObjectByID( m_bestTargetID );
  183. if( target && m_moveRange == 0.0f )
  184. {
  185. WeaponBonus bonus;
  186. bonus.clear();
  187. Real fireRange = m_weaponTemplate->getAttackRange( bonus );
  188. Object *me = getObject();
  189. Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) );
  190. if( fDist < fireRange )
  191. {
  192. //We are currently in range!
  193. m_inRange = true;
  194. }
  195. else
  196. {
  197. if( m_inRange )
  198. {
  199. //We were in range last frame, but the target has moved out of firing range, so
  200. //re-evaluate by forcing a new scan.
  201. m_nextScanFrames = GameLogicRandomValue( 0, 3 );
  202. m_bestTargetID = INVALID_ID;
  203. if( !m_nextScanFrames )
  204. {
  205. scanClosestTarget();
  206. m_nextScanFrames = data->m_scanFrames;
  207. target = NULL; //Set target to NULL so we don't shoot at it (might be out of range)
  208. }
  209. }
  210. else
  211. {
  212. //Not in range
  213. m_inRange = false;
  214. }
  215. }
  216. }
  217. if( m_nextShotAvailableInFrames > 0 )
  218. {
  219. //We can't fire this frame.
  220. m_nextShotAvailableInFrames--;
  221. return;
  222. }
  223. //Fire control!
  224. if( target )
  225. {
  226. AIUpdateInterface *ai = self->getAI();
  227. if( ai )
  228. {
  229. if( ai->isIdle() || ai->isBusy() )
  230. {
  231. // lock it just till the weapon is empty or the attack is "done"
  232. self->setWeaponLock( data->m_weaponSlot, LOCKED_TEMPORARILY );
  233. ai->aiAttackObject( target, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI );
  234. }
  235. }
  236. }
  237. }
  238. //-------------------------------------------------------------------------------------------------
  239. Object* CleanupHazardUpdate::scanClosestTarget()
  240. {
  241. const CleanupHazardUpdateModuleData *data = getCleanupHazardUpdateModuleData();
  242. Object *me = getObject();
  243. Object *bestTargetInRange = NULL;
  244. m_bestTargetID = INVALID_ID;
  245. PartitionFilterAcceptByKindOf kindFilter(MAKE_KINDOF_MASK(KINDOF_CLEANUP_HAZARD), KINDOFMASK_NONE);
  246. PartitionFilterSameMapStatus filterMapStatus(getObject());
  247. PartitionFilter* filters[] = { &kindFilter, &filterMapStatus, NULL };
  248. if( m_moveRange > 0.0f )
  249. {
  250. //Look for targets around the target position only (but add scan range and move range).
  251. //This case only happens when we are performing a cleanup area command.
  252. bestTargetInRange = ThePartitionManager->getClosestObject( &m_pos, data->m_scanRange + m_moveRange, FROM_CENTER_2D, filters );
  253. }
  254. else
  255. {
  256. //Look for targets near me -- passive default.
  257. bestTargetInRange = ThePartitionManager->getClosestObject( me->getPosition(), data->m_scanRange, FROM_CENTER_2D, filters );
  258. }
  259. if( bestTargetInRange )
  260. {
  261. m_bestTargetID = bestTargetInRange->getID();
  262. }
  263. return bestTargetInRange;
  264. }
  265. //-------------------------------------------------------------------------------------------------
  266. //This allows the unit to cleanup an area until clean, then the AI goes idle.
  267. //-------------------------------------------------------------------------------------------------
  268. void CleanupHazardUpdate::setCleanupAreaParameters( const Coord3D *pos, Real range )
  269. {
  270. Object *obj = getObject();
  271. AIUpdateInterface *ai = obj->getAI();
  272. //Setting the move range triggers that passive conditions for it to be allowed to move
  273. //from the specified position.
  274. m_moveRange = range;
  275. m_pos = *pos;
  276. if( ai )
  277. {
  278. //CMD_FROM_AI important because it'll abort when other types are used -- like if a player
  279. //or script orders the unit to do something else, we need a way to cancel this passive
  280. //situation.
  281. ai->aiMoveToPosition( pos, CMD_FROM_AI );
  282. }
  283. }
  284. // ------------------------------------------------------------------------------------------------
  285. /** CRC */
  286. // ------------------------------------------------------------------------------------------------
  287. void CleanupHazardUpdate::crc( Xfer *xfer )
  288. {
  289. // extend base class
  290. UpdateModule::crc( xfer );
  291. } // end crc
  292. // ------------------------------------------------------------------------------------------------
  293. /** Xfer method
  294. * Version Info:
  295. * 1: Initial version */
  296. // ------------------------------------------------------------------------------------------------
  297. void CleanupHazardUpdate::xfer( Xfer *xfer )
  298. {
  299. // version
  300. XferVersion currentVersion = 1;
  301. XferVersion version = currentVersion;
  302. xfer->xferVersion( &version, currentVersion );
  303. // extend base class
  304. UpdateModule::xfer( xfer );
  305. // best target id
  306. xfer->xferObjectID( &m_bestTargetID );
  307. // in range
  308. xfer->xferBool( &m_inRange );
  309. // next scan frames
  310. xfer->xferInt( &m_nextScanFrames );
  311. // next shot available in frames
  312. xfer->xferInt( &m_nextShotAvailableInFrames );
  313. // don't need to save weapon template, it's retrieved onObjectCreated
  314. // const WeaponTemplate *m_weaponTemplate;
  315. // pos
  316. xfer->xferCoord3D( &m_pos );
  317. // move range
  318. xfer->xferReal( &m_moveRange );
  319. } // end xfer
  320. // ------------------------------------------------------------------------------------------------
  321. /** Load post process */
  322. // ------------------------------------------------------------------------------------------------
  323. void CleanupHazardUpdate::loadPostProcess( void )
  324. {
  325. // extend base class
  326. UpdateModule::loadPostProcess();
  327. } // end loadPostProcess