WeaponSet.cpp 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  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: WeaponSet.cpp ///////////////////////////////////////////////////////////////////////////////
  24. // Author: Steven Johnson, March 2002
  25. // Desc: Weapon descriptions
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  29. #define DEFINE_WEAPONSLOTTYPE_NAMES
  30. #define DEFINE_COMMANDSOURCEMASK_NAMES
  31. #include "GameLogic/WeaponSet.h"
  32. #include "Common/INI.h"
  33. #include "Common/Player.h"
  34. #include "Common/PlayerList.h"
  35. #include "Common/ThingFactory.h"
  36. #include "Common/ThingTemplate.h"
  37. #include "Common/BitFlagsIO.h"
  38. #include "GameLogic/AI.h"
  39. #include "GameLogic/Object.h"
  40. #include "GameLogic/Module/AIUpdate.h"
  41. #include "GameLogic/Module/ContainModule.h"
  42. #include "GameLogic/Module/StealthUpdate.h"
  43. #include "GameLogic/Module/SpawnBehavior.h"
  44. #include "GameLogic/Weapon.h"
  45. #ifdef _INTERNAL
  46. // for occasional debugging...
  47. //#pragma optimize("", off)
  48. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  49. #endif
  50. ///////////////////////////////////////////////////////////////////////////////////////////////////
  51. // PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
  52. ///////////////////////////////////////////////////////////////////////////////////////////////////
  53. const char* WeaponSetFlags::s_bitNameList[] =
  54. {
  55. "VETERAN",
  56. "ELITE",
  57. "HERO",
  58. "PLAYER_UPGRADE",
  59. "CRATEUPGRADE_ONE",
  60. "CRATEUPGRADE_TWO",
  61. "VEHICLE_HIJACK",
  62. "CARBOMB",
  63. "MINE_CLEARING_DETAIL",
  64. NULL
  65. };
  66. ///////////////////////////////////////////////////////////////////////////////////////////////////
  67. // PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
  68. ///////////////////////////////////////////////////////////////////////////////////////////////////
  69. ///////////////////////////////////////////////////////////////////////////////////////////////////
  70. // PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
  71. ///////////////////////////////////////////////////////////////////////////////////////////////////
  72. //-------------------------------------------------------------------------------------------------
  73. //-------------------------------------------------------------------------------------------------
  74. //-------------------------------------------------------------------------------------------------
  75. //-------------------------------------------------------------------------------------------------
  76. void WeaponTemplateSet::clear()
  77. {
  78. m_isReloadTimeShared = false;
  79. m_isWeaponLockSharedAcrossSets = FALSE;
  80. m_types.clear();
  81. for (int i = 0; i < WEAPONSLOT_COUNT; ++i)
  82. {
  83. m_template[i] = NULL;
  84. m_autoChooseMask[i] = 0xffffffff; // by default, allow autochoosing from any CommandSource
  85. CLEAR_KINDOFMASK(m_preferredAgainst[i]); // by default, weapon isn't preferred against anything in particular
  86. }
  87. }
  88. //-------------------------------------------------------------------------------------------------
  89. Bool WeaponTemplateSet::hasAnyWeapons() const
  90. {
  91. for (int i = 0; i < WEAPONSLOT_COUNT; ++i)
  92. {
  93. if (m_template[i])
  94. return true;
  95. }
  96. return false;
  97. }
  98. //-------------------------------------------------------------------------------------------------
  99. void WeaponTemplateSet::parseWeapon(INI* ini, void *instance, void * /*store*/, const void* userData)
  100. {
  101. WeaponTemplateSet* self = (WeaponTemplateSet*)instance;
  102. WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(ini->getNextToken(), TheWeaponSlotTypeNames);
  103. INI::parseWeaponTemplate(ini, instance, &self->m_template[wslot], NULL);
  104. }
  105. //-------------------------------------------------------------------------------------------------
  106. void WeaponTemplateSet::parseAutoChoose(INI* ini, void *instance, void * /*store*/, const void* userData)
  107. {
  108. WeaponTemplateSet* self = (WeaponTemplateSet*)instance;
  109. WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(ini->getNextToken(), TheWeaponSlotTypeNames);
  110. INI::parseBitString32(ini, instance, &self->m_autoChooseMask[wslot], TheCommandSourceMaskNames);
  111. }
  112. //-------------------------------------------------------------------------------------------------
  113. void WeaponTemplateSet::parsePreferredAgainst(INI* ini, void *instance, void * /*store*/, const void* userData)
  114. {
  115. WeaponTemplateSet* self = (WeaponTemplateSet*)instance;
  116. WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(ini->getNextToken(), TheWeaponSlotTypeNames);
  117. KindOfMaskType::parseFromINI(ini, instance, &self->m_preferredAgainst[wslot], NULL);
  118. }
  119. //-------------------------------------------------------------------------------------------------
  120. void WeaponTemplateSet::parseWeaponTemplateSet( INI* ini, const ThingTemplate* tt )
  121. {
  122. static const FieldParse myFieldParse[] =
  123. {
  124. { "Conditions", WeaponSetFlags::parseFromINI, NULL, offsetof( WeaponTemplateSet, m_types ) },
  125. { "Weapon", WeaponTemplateSet::parseWeapon, NULL, 0 },
  126. { "AutoChooseSources", WeaponTemplateSet::parseAutoChoose, NULL, 0 },
  127. { "PreferredAgainst", WeaponTemplateSet::parsePreferredAgainst, NULL, 0 },
  128. { "ShareWeaponReloadTime", INI::parseBool, NULL, offsetof( WeaponTemplateSet, m_isReloadTimeShared ) },
  129. { "WeaponLockSharedAcrossSets", INI::parseBool, NULL, offsetof( WeaponTemplateSet, m_isWeaponLockSharedAcrossSets ) },
  130. { 0, 0, 0, 0 }
  131. };
  132. ini->initFromINI(this, myFieldParse);
  133. this->m_thingTemplate = tt;
  134. }
  135. //-------------------------------------------------------------------------------------------------
  136. Bool WeaponTemplateSet::testWeaponSetFlag( WeaponSetType wst ) const
  137. {
  138. return m_types.test( wst );
  139. }
  140. //-------------------------------------------------------------------------------------------------
  141. //-------------------------------------------------------------------------------------------------
  142. //-------------------------------------------------------------------------------------------------
  143. //-------------------------------------------------------------------------------------------------
  144. WeaponSet::WeaponSet()
  145. {
  146. m_curWeapon = PRIMARY_WEAPON;
  147. m_curWeaponLockedStatus = NOT_LOCKED;
  148. m_curWeaponTemplateSet = NULL;
  149. m_filledWeaponSlotMask = 0;
  150. m_totalAntiMask = 0;
  151. m_totalDamageTypeMask = 0;
  152. DEBUG_ASSERTCRASH(DAMAGE_NUM_TYPES <= 32, ("m_totalDamageTypeMask will need to be enlarged in WeaponSet"));
  153. m_hasPitchLimit = false;
  154. m_hasDamageWeapon = false;
  155. for (Int i = 0; i < WEAPONSLOT_COUNT; ++i)
  156. m_weapons[i] = NULL;
  157. }
  158. //-------------------------------------------------------------------------------------------------
  159. WeaponSet::~WeaponSet()
  160. {
  161. for (Int i = 0; i < WEAPONSLOT_COUNT; ++i)
  162. if (m_weapons[i])
  163. m_weapons[i]->deleteInstance();
  164. }
  165. // ------------------------------------------------------------------------------------------------
  166. /** CRC */
  167. // ------------------------------------------------------------------------------------------------
  168. void WeaponSet::crc( Xfer *xfer )
  169. {
  170. } // end crc
  171. // ------------------------------------------------------------------------------------------------
  172. /** Xfer method
  173. * Version Info:
  174. * 1: Initial version */
  175. // ------------------------------------------------------------------------------------------------
  176. void WeaponSet::xfer( Xfer *xfer )
  177. {
  178. // version
  179. const XferVersion currentVersion = 1;
  180. XferVersion version = currentVersion;
  181. xfer->xferVersion( &version, currentVersion );
  182. if (xfer->getXferMode() == XFER_LOAD)
  183. {
  184. AsciiString ttName;
  185. WeaponSetFlags wsFlags;
  186. xfer->xferAsciiString(&ttName);
  187. wsFlags.xfer( xfer );
  188. if (ttName.isEmpty())
  189. {
  190. m_curWeaponTemplateSet = NULL;
  191. }
  192. else
  193. {
  194. const ThingTemplate* tt = TheThingFactory->findTemplate(ttName);
  195. if (tt == NULL)
  196. throw INI_INVALID_DATA;
  197. m_curWeaponTemplateSet = tt->findWeaponTemplateSet(wsFlags);
  198. if (m_curWeaponTemplateSet == NULL)
  199. throw INI_INVALID_DATA;
  200. }
  201. }
  202. else if (xfer->getXferMode() == XFER_SAVE)
  203. {
  204. AsciiString ttName; // leave 'em empty in case we're null
  205. WeaponSetFlags wsFlags;
  206. if (m_curWeaponTemplateSet != NULL)
  207. {
  208. const ThingTemplate* tt = m_curWeaponTemplateSet->friend_getThingTemplate();
  209. if (tt == NULL)
  210. throw INI_INVALID_DATA;
  211. ttName = tt->getName();
  212. wsFlags = m_curWeaponTemplateSet->friend_getWeaponSetFlags();
  213. }
  214. xfer->xferAsciiString(&ttName);
  215. wsFlags.xfer( xfer );
  216. }
  217. for (Int i = 0; i < WEAPONSLOT_COUNT; ++i)
  218. {
  219. Bool hasWeaponInSlot = (m_weapons[i] != NULL);
  220. xfer->xferBool(&hasWeaponInSlot);
  221. if (hasWeaponInSlot)
  222. {
  223. if (xfer->getXferMode() == XFER_LOAD && m_weapons[i] == NULL)
  224. {
  225. const WeaponTemplate* wt = m_curWeaponTemplateSet->getNth((WeaponSlotType)i);
  226. if (wt==NULL) {
  227. DEBUG_CRASH(("xfer backwards compatibility code - old save file??? jba."));
  228. wt = m_curWeaponTemplateSet->getNth((WeaponSlotType)0);
  229. }
  230. m_weapons[i] = TheWeaponStore->allocateNewWeapon(wt, (WeaponSlotType)i);
  231. }
  232. xfer->xferSnapshot(m_weapons[i]);
  233. }
  234. }
  235. xfer->xferUser(&m_curWeapon, sizeof(m_curWeapon));
  236. xfer->xferUser(&m_curWeaponLockedStatus, sizeof(m_curWeaponLockedStatus));
  237. xfer->xferUnsignedInt(&m_filledWeaponSlotMask);
  238. xfer->xferInt(&m_totalAntiMask);
  239. xfer->xferUnsignedInt(&m_totalDamageTypeMask);
  240. xfer->xferBool(&m_hasDamageWeapon);
  241. xfer->xferBool(&m_hasDamageWeapon);
  242. }
  243. // ------------------------------------------------------------------------------------------------
  244. /** Load post process */
  245. // ------------------------------------------------------------------------------------------------
  246. void WeaponSet::loadPostProcess( void )
  247. {
  248. } // end loadPostProcess
  249. //-------------------------------------------------------------------------------------------------
  250. void WeaponSet::updateWeaponSet(const Object* obj)
  251. {
  252. const WeaponTemplateSet* set = obj->getTemplate()->findWeaponTemplateSet(obj->getWeaponSetFlags());
  253. DEBUG_ASSERTCRASH(set, ("findWeaponSet should never return null"));
  254. if (set && set != m_curWeaponTemplateSet)
  255. {
  256. if( ! set->isWeaponLockSharedAcrossSets() )
  257. {
  258. DEBUG_ASSERTLOG(!isCurWeaponLocked(), ("changing WeaponSet while Weapon is Locked... implicit unlock occurring!\n"));
  259. releaseWeaponLock(LOCKED_PERMANENTLY); // release all locks. sorry!
  260. m_curWeapon = PRIMARY_WEAPON;
  261. }
  262. m_filledWeaponSlotMask = 0;
  263. m_totalAntiMask = 0;
  264. m_totalDamageTypeMask = 0;
  265. m_hasPitchLimit = false;
  266. m_hasDamageWeapon = false;
  267. for (Int i = WEAPONSLOT_COUNT - 1; i >= PRIMARY_WEAPON ; --i)
  268. {
  269. if (m_weapons[i] != NULL)
  270. {
  271. m_weapons[i]->deleteInstance();
  272. m_weapons[i] = NULL;
  273. }
  274. if (set->getNth((WeaponSlotType)i))
  275. {
  276. m_weapons[i] = TheWeaponStore->allocateNewWeapon(set->getNth((WeaponSlotType)i), (WeaponSlotType)i);
  277. m_weapons[i]->loadAmmoNow(obj); // start 'em all with full clips.
  278. m_filledWeaponSlotMask |= (1 << i);
  279. m_totalAntiMask |= m_weapons[i]->getAntiMask();
  280. m_totalDamageTypeMask |= (1 << m_weapons[i]->getDamageType());
  281. if (m_weapons[i]->isPitchLimited())
  282. m_hasPitchLimit = true;
  283. if (m_weapons[i]->isDamageWeapon())
  284. m_hasDamageWeapon = true;
  285. // no, do NOT do this; always start with the cur weapon being primary, even if there is no primary
  286. // weapon. this is by design, to allow us to have units that have only "spell" weapons and no
  287. // "normal" weapons. (srj)
  288. // m_curWeapon = (WeaponSlotType)i;
  289. }
  290. }
  291. m_curWeaponTemplateSet = set;
  292. //DEBUG_LOG(("WeaponSet::updateWeaponSet -- changed curweapon to %s\n",getCurWeapon()->getName().str()));
  293. }
  294. }
  295. //-------------------------------------------------------------------------------------------------
  296. /*static*/ ModelConditionFlags WeaponSet::getModelConditionForWeaponSlot(WeaponSlotType wslot, WeaponSetConditionType a)
  297. {
  298. static const ModelConditionFlagType Nothing[WEAPONSLOT_COUNT] = { MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID };
  299. static const ModelConditionFlagType Firing[WEAPONSLOT_COUNT] = { MODELCONDITION_FIRING_A, MODELCONDITION_FIRING_B, MODELCONDITION_FIRING_C };
  300. static const ModelConditionFlagType Betweening[WEAPONSLOT_COUNT] = { MODELCONDITION_BETWEEN_FIRING_SHOTS_A, MODELCONDITION_BETWEEN_FIRING_SHOTS_B, MODELCONDITION_BETWEEN_FIRING_SHOTS_C };
  301. static const ModelConditionFlagType Reloading[WEAPONSLOT_COUNT] = { MODELCONDITION_RELOADING_A, MODELCONDITION_RELOADING_B, MODELCONDITION_RELOADING_C };
  302. static const ModelConditionFlagType PreAttack[WEAPONSLOT_COUNT] = { MODELCONDITION_PREATTACK_A, MODELCONDITION_PREATTACK_B, MODELCONDITION_PREATTACK_C };
  303. static const ModelConditionFlagType* Lookup[WSF_COUNT] = { Nothing, Firing, Betweening, Reloading, PreAttack };
  304. ModelConditionFlags flags; // defaults to all clear
  305. ModelConditionFlagType f = Lookup[a][wslot];
  306. if (f != MODELCONDITION_INVALID)
  307. flags.set(f);
  308. static const ModelConditionFlagType Using[WEAPONSLOT_COUNT] = { MODELCONDITION_USING_WEAPON_A, MODELCONDITION_USING_WEAPON_B, MODELCONDITION_USING_WEAPON_C };
  309. if (a != WSF_NONE)
  310. flags.set(Using[wslot]);
  311. return flags;
  312. }
  313. //-------------------------------------------------------------------------------------------------
  314. static Int getVictimAntiMask(const Object* victim)
  315. {
  316. if( victim->isKindOf( KINDOF_MINE ) )
  317. {
  318. return WEAPON_ANTI_MINE | WEAPON_ANTI_GROUND;
  319. }
  320. else if( victim->isKindOf( KINDOF_SMALL_MISSILE ) )
  321. {
  322. //All missiles are also projectiles!
  323. return WEAPON_ANTI_SMALL_MISSILE;
  324. }
  325. else if( victim->isKindOf( KINDOF_BALLISTIC_MISSILE ) )
  326. {
  327. return WEAPON_ANTI_BALLISTIC_MISSILE;
  328. }
  329. else if( victim->isKindOf( KINDOF_PROJECTILE ) )
  330. {
  331. return WEAPON_ANTI_PROJECTILE;
  332. }
  333. else if( victim->isAirborneTarget() )
  334. {
  335. if( victim->isKindOf( KINDOF_VEHICLE ) )
  336. {
  337. return WEAPON_ANTI_AIRBORNE_VEHICLE;
  338. }
  339. else if( victim->isKindOf( KINDOF_INFANTRY ) )
  340. {
  341. return WEAPON_ANTI_AIRBORNE_INFANTRY;
  342. }
  343. else if( victim->isKindOf( KINDOF_PARACHUTE ) )
  344. {
  345. return WEAPON_ANTI_PARACHUTE;
  346. }
  347. else if( !victim->isKindOf( KINDOF_UNATTACKABLE ) )
  348. {
  349. DEBUG_CRASH( ("Object %s is being targetted as airborne, but is not infantry, nor vehicle. Is this legit? -- tell Kris", victim->getTemplate()->getName().str() ) );
  350. }
  351. return 0;
  352. }
  353. else
  354. {
  355. return WEAPON_ANTI_GROUND;
  356. }
  357. }
  358. //-------------------------------------------------------------------------------------------------
  359. Bool WeaponSet::isAnyWithinTargetPitch(const Object* obj, const Object* victim) const
  360. {
  361. if (!m_hasPitchLimit)
  362. return true;
  363. for( Int i = 0; i < WEAPONSLOT_COUNT; i++ )
  364. {
  365. const Weapon* weapon = m_weapons[ i ];
  366. if (weapon && weapon->isWithinTargetPitch(obj, victim))
  367. {
  368. return true;
  369. }
  370. }
  371. return false;
  372. }
  373. //-------------------------------------------------------------------------------------------------
  374. CanAttackResult WeaponSet::getAbleToAttackSpecificObject( AbleToAttackType attackType, const Object* source, const Object* victim, CommandSourceType commandSource ) const
  375. {
  376. static NameKeyType key_StealthUpdate = NAMEKEY( "StealthUpdate" );
  377. // basic sanity checks.
  378. if (!source ||
  379. !victim ||
  380. source->isEffectivelyDead() ||
  381. victim->isEffectivelyDead() ||
  382. source->isDestroyed() ||
  383. victim->isDestroyed() ||
  384. victim == source)
  385. return ATTACKRESULT_NOT_POSSIBLE;
  386. Bool sameOwnerForceAttack = ((source->getControllingPlayer() == victim->getControllingPlayer()) && isForcedAttack(attackType));
  387. // ----- examine VICTIM to determine legality of attack
  388. //
  389. // an "OBJECT_STATUS_MASKED" object is not really a valid target for attacking, this masked
  390. // status is an "override" that can be applied to objects that are otherwise totally
  391. // valid targets when we really really really don't want anything in the world
  392. // being able to attack stuff
  393. //
  394. if (victim->testStatus(OBJECT_STATUS_MASKED))
  395. return ATTACKRESULT_NOT_POSSIBLE;
  396. // And this is the KINDOF version, for things that aren't really game objects, but need to have "life"
  397. // so they can have lifetimeupdates and vision
  398. if (victim->isKindOf(KINDOF_UNATTACKABLE))
  399. return ATTACKRESULT_NOT_POSSIBLE;
  400. // this object is not currently auto-acquireable
  401. if (victim->testStatus(OBJECT_STATUS_NO_ATTACK_FROM_AI) && commandSource == CMD_FROM_AI)
  402. return ATTACKRESULT_NOT_POSSIBLE;
  403. Bool allowStealthToPreventAttacks = TRUE;
  404. if (source->testStatus(OBJECT_STATUS_IGNORING_STEALTH) || sameOwnerForceAttack)
  405. allowStealthToPreventAttacks = FALSE;
  406. if( isForcedAttack( attackType ) && victim->isKindOf( KINDOF_DISGUISER ) )
  407. {
  408. // force-attack allows you to attack disguised things, which also happen to be stealthed.
  409. // since we normally disallow attacking stealthed things (even via force-fire), we check
  410. // for disguised and explicitly ignore stealth in that case
  411. StealthUpdate *update = (StealthUpdate*)victim->findUpdateModule( key_StealthUpdate );
  412. if (update && update->isDisguised())
  413. allowStealthToPreventAttacks = FALSE;
  414. }
  415. // If an object is stealthed and hasn't been detected yet, then it is not a valid target to fire
  416. // on.
  417. if (allowStealthToPreventAttacks &&
  418. victim->testStatus(OBJECT_STATUS_STEALTHED) &&
  419. !victim->testStatus(OBJECT_STATUS_DETECTED))
  420. {
  421. if( !victim->isKindOf( KINDOF_DISGUISER ) )
  422. {
  423. return ATTACKRESULT_NOT_POSSIBLE;
  424. }
  425. else
  426. {
  427. //Exception case -- don't return false if we are a bomb truck disguised as an enemy vehicle.
  428. StealthUpdate *update = (StealthUpdate*)victim->findUpdateModule( key_StealthUpdate );
  429. if( update && update->isDisguised() )
  430. {
  431. Player *ourPlayer = source->getControllingPlayer();
  432. Player *otherPlayer = ThePlayerList->getNthPlayer( update->getDisguisedPlayerIndex() );
  433. if( ourPlayer && otherPlayer )
  434. {
  435. if( ourPlayer->getRelationship( otherPlayer->getDefaultTeam() ) != ENEMIES )
  436. {
  437. //Our stealthed & undetected object is disguised as a unit not perceived to be our enemy.
  438. return ATTACKRESULT_NOT_POSSIBLE;
  439. }
  440. }
  441. }
  442. }
  443. }
  444. // If the victim is fully fogged or fully shrouded, then we cannot attack them
  445. // GS -- Shroud only applies in decision making to Player owned objects and when the source is not from script
  446. // SRJ -- ignore shroud checks if mode is CONTINUED and the target is immobile, since presumably
  447. // we know where it is (and it's not going anywhere); this prevents wonky behavior if you have planes attack
  448. // a deshrouded thing, which becomes reshrouded before they get there (basically the planes circle once in order
  449. // to deshroud it again, and get nailed by AA fire in the meantime)
  450. // GS -- Fog no longer prevents attacking at all. it prevents targeting in findClosestEnemy. Leaving this
  451. // here commented out to show that shroud should not be checked.
  452. // const Player* sourceController = source->getControllingPlayer();
  453. // if( sourceController
  454. // && sourceController->getPlayerType() == PLAYER_HUMAN
  455. // && commandSource != CMD_FROM_SCRIPT
  456. // && !(isContinuedAttack(attackType) && victim->isKindOf(KINDOF_IMMOBILE))
  457. // && victim->getShroudedStatus(sourceController->getPlayerIndex()) >= OBJECTSHROUD_FOGGED
  458. // )
  459. // {
  460. // return ATTACKRESULT_NOT_POSSIBLE;
  461. // }
  462. //
  463. // At least one of the following must be true:
  464. //
  465. // -- victim is our enemy
  466. // -- victim is a Bridge Tower (they are always neutral)
  467. // -- victim is a non-allied Mine
  468. // -- attack mode is force attack
  469. //
  470. // if none of the above is true, we can't attack. sorry!
  471. //
  472. Relationship r = source->getRelationship(victim);
  473. if (r != ENEMIES &&
  474. !isForcedAttack(attackType) &&
  475. // GS !victim->isKindOf( KINDOF_BRIDGE_TOWER ) && // Repairing bridges cut 12/12/02, so attacking is too
  476. !(victim->isKindOf( KINDOF_MINE ) && r != ALLIES))
  477. {
  478. //Only reject this if the command is from a player. If scripts or AI order attacks, they don't
  479. //care about relationships (and fixes broken scripts).
  480. if( commandSource == CMD_FROM_PLAYER && (!victim->testScriptStatusBit( OBJECT_STATUS_SCRIPT_TARGETABLE ) || r == ALLIES) )
  481. {
  482. //Unless the object has a map propertly that sets it to be targetable (and not allied), then give up.
  483. return ATTACKRESULT_NOT_POSSIBLE;
  484. }
  485. }
  486. // if the victim is contained within an enclosing container, it cannot be attacked directly
  487. const Object* victimsContainer = victim->getContainedBy();
  488. if (victimsContainer != NULL && victimsContainer->getContain()->isEnclosingContainerFor(victim) == TRUE)
  489. {
  490. return ATTACKRESULT_NOT_POSSIBLE;
  491. }
  492. // check if the container has an apparent controller, that controller must be an enemy
  493. // (unless this is a force-attack)
  494. if (!isForcedAttack(attackType))
  495. {
  496. const ContainModuleInterface* victimContain = victim->getContain();
  497. if (victimContain)
  498. {
  499. const Player* victimApparentController = victimContain->getApparentControllingPlayer(source->getControllingPlayer());
  500. if (victimApparentController && source->getTeam()->getRelationship(victimApparentController->getDefaultTeam()) != ENEMIES)
  501. {
  502. //Only reject this if the command is from a player. If scripts or AI order attacks, they don't
  503. //care about relationships (and fixes broken scripts).
  504. if( commandSource == CMD_FROM_PLAYER && (!victim->testScriptStatusBit( OBJECT_STATUS_SCRIPT_TARGETABLE ) || r == ALLIES) )
  505. {
  506. //Unless the object has a map propertly that sets it to be targetable (and not allied), then give up.
  507. return ATTACKRESULT_NOT_POSSIBLE;
  508. }
  509. }
  510. }
  511. }
  512. //Check if the shot itself is valid!
  513. return getAbleToUseWeaponAgainstTarget( attackType, source, victim, victim->getPosition(), commandSource );
  514. }
  515. //-------------------------------------------------------------------------------------------------
  516. //This is formerly the 2nd half of getAbleToAttackSpecificObject
  517. //This function is responsible for determining if our object is physically capable of attacking the target and it
  518. //supports both victim or position.
  519. CanAttackResult WeaponSet::getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *source, const Object *victim, const Coord3D *pos, CommandSourceType commandSource ) const
  520. {
  521. //First determine if we are attacking an object or the ground and get the
  522. //appropriate weapon anti mask.
  523. WeaponAntiMaskType targetAntiMask;
  524. if( victim )
  525. {
  526. //Attacking a specific object -- get the victim's anti weapon mask!
  527. targetAntiMask = (WeaponAntiMaskType)getVictimAntiMask( victim );
  528. //Make sure that the pos matches the victim!
  529. pos = victim->getPosition();
  530. }
  531. else
  532. {
  533. //Attacking the ground so this is obvious.
  534. targetAntiMask = WEAPON_ANTI_GROUND;
  535. }
  536. // Special Case test for turreted weapons on buildings... since they cannot move, they must be within range of target
  537. //Kris: Actually -- let's just do this for all immobile objects. If we can give it orders to attack, then we must check
  538. // the range anyways. This will also fix stinger soldiers.
  539. //Kris: ***NOTE*** KINDOF_SPAWNS_ARE_THE_WEAPONS was added here because initially I tried to get this to work by
  540. // making stinger soldiers immobile. However, doing so prevents them from attacking because they can't turn without
  541. // a locomotor! In anycase, the stinger site when ordered to attack will pass the spawn member closest to the target
  542. // to this function for evaluation in it's place. So to get that code into this area, giving it the kindof bit will
  543. // accomplish that. This is fine because only these two units have this set. Should that change in the future, this
  544. // will need re-evaluation (can be solved with a new kindof).
  545. //
  546. // srj sez: contained soldiers are a lot like turrets in this sense. they should only attempt to acquire targets
  547. // within range, even if their container is mobile. otherwise they end up resetting their mood-check
  548. // times in odd ways in some cases. hence the getContainedBy() check.
  549. //Kris: Dec 28 -- Now we're testing this for ALL objects, but we only abort if the object is immobile. We'll use the
  550. // information later to determine if the unit must move before firing (cursor reasons).
  551. Bool withinAttackRange = FALSE;
  552. const Object *containedBy = source->getContainedBy();
  553. Bool hasAWeaponInRange = FALSE;
  554. Bool hasAWeapon = FALSE;
  555. for (Int slot = 0; slot < WEAPONSLOT_COUNT - 1; ++slot)
  556. {
  557. Weapon *weaponToTestForRange = m_weapons[ m_curWeapon ];
  558. if ( weaponToTestForRange )
  559. {
  560. hasAWeapon = TRUE;
  561. if ((m_totalAntiMask & targetAntiMask) == 0)//we don't care to check for this weapon
  562. continue;
  563. Bool handled = FALSE;
  564. ContainModuleInterface *contain = containedBy ? containedBy->getContain() : NULL;
  565. if( contain && contain->isGarrisonable() )
  566. {
  567. //For contained things, we need to fake-move objects to the best garrison point in order
  568. //to get precise range checks.
  569. Coord3D targetPos = *pos;
  570. Coord3D goalPos;
  571. if( contain->calcBestGarrisonPosition( &goalPos, &targetPos ) )
  572. {
  573. withinAttackRange = weaponToTestForRange->isSourceObjectWithGoalPositionWithinAttackRange( source, &goalPos, victim, &targetPos );
  574. handled = TRUE;
  575. }
  576. }
  577. else if( victim )
  578. withinAttackRange = weaponToTestForRange->isWithinAttackRange( source, victim );
  579. else
  580. withinAttackRange = weaponToTestForRange->isWithinAttackRange( source, pos );
  581. if( withinAttackRange )
  582. {
  583. hasAWeaponInRange = TRUE;
  584. break;
  585. }
  586. }
  587. }
  588. if( source->isKindOf( KINDOF_IMMOBILE ) || source->isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) || containedBy )
  589. {
  590. if ( hasAWeapon && !hasAWeaponInRange && attackType != ATTACK_TUNNEL_NETWORK_GUARD )
  591. return ATTACKRESULT_INVALID_SHOT;
  592. }
  593. CanAttackResult okResult = withinAttackRange ? ATTACKRESULT_POSSIBLE : ATTACKRESULT_POSSIBLE_AFTER_MOVING;
  594. // ----- things we examine about SOURCE to determine legality of attack
  595. if (hasAnyDamageWeapon())
  596. {
  597. if ((m_totalAntiMask & targetAntiMask) == 0)
  598. return ATTACKRESULT_INVALID_SHOT;
  599. //If we don't have a victim, we are force attacking a position. Because
  600. //of the weapon mask check preceding this, the attack is possible!
  601. if( !victim )
  602. return okResult;
  603. if (!isAnyWithinTargetPitch(source, victim))
  604. return ATTACKRESULT_INVALID_SHOT;
  605. Int first, last;
  606. if( isCurWeaponLocked() )
  607. {
  608. first = m_curWeapon;
  609. last = m_curWeapon;
  610. }
  611. else
  612. {
  613. first = WEAPONSLOT_COUNT - 1;
  614. last = PRIMARY_WEAPON;
  615. }
  616. //Check each weapon, until we find one that can do damage (or fail)
  617. for( Int i = first; i >= last ; --i )
  618. {
  619. Weapon *weapon = m_weapons[ i ];
  620. if (weapon && weapon->estimateWeaponDamage( source, victim ))
  621. {
  622. return okResult;
  623. }
  624. }
  625. }
  626. // Do a check to see if we have an occupied container (garrisoned building, transport that allows passengers to fire).
  627. ContainModuleInterface *contain = source->getContain();
  628. if (contain && contain->isPassengerAllowedToFire())
  629. {
  630. // Loop through each member and if just one of them can attack the specific target, then
  631. // we are good to go!
  632. const ContainedItemsList* items = contain->getContainedItemsList();
  633. if (items)
  634. {
  635. for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it)
  636. {
  637. Object* garrisonedMember = *it;
  638. if( garrisonedMember->isAbleToAttack() )
  639. {
  640. CanAttackResult result = garrisonedMember->getAbleToUseWeaponAgainstTarget( attackType, victim, pos, commandSource );
  641. if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
  642. {
  643. return result;
  644. }
  645. }
  646. }
  647. }
  648. }
  649. // Do a check to see if we have a hive object that has slaved objects.
  650. SpawnBehaviorInterface* spawnInterface = source->getSpawnBehaviorInterface();
  651. if( spawnInterface &&
  652. spawnInterface->getCanAnySlavesUseWeaponAgainstTarget( attackType, victim, pos, commandSource ) == ATTACKRESULT_POSSIBLE )
  653. {
  654. // srj sez: this bit is intended to fix the situation where you have a stinger site
  655. // selected and get the "attack if I move" cursor against an enemy. since the stinger site can't
  656. // move or fire, you shouldn't really EVER get this cursor for it. and since we just verified above
  657. // that our slaves (the soldiers) can attack correctly, just nork it.
  658. if (source->isKindOf( KINDOF_IMMOBILE )
  659. && source->isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )
  660. && okResult == ATTACKRESULT_POSSIBLE_AFTER_MOVING)
  661. okResult = ATTACKRESULT_POSSIBLE;
  662. return okResult;
  663. }
  664. // Oh well... guess not!
  665. return ATTACKRESULT_INVALID_SHOT;
  666. }
  667. //-------------------------------------------------------------------------------------------------
  668. Bool WeaponSet::chooseBestWeaponForTarget(const Object* obj, const Object* victim, WeaponChoiceCriteria criteria, CommandSourceType cmdSource)
  669. {
  670. /*
  671. 1) The first criteria is weapon fitness. If the object has two weapons that can fire concurrently,
  672. find the set of weapons that can hit the given target. If two weapons can hit the given target,
  673. 2) Figure potential damage. If both weapons have the same potential damage (should never happen) then,
  674. 3) Pick one.
  675. Another consideration is reloading. A weapon that is not ready to fire right this moment should
  676. never be returned if there is another weapon that could be fired. Readiness trumps Criteria.
  677. Note that this will never result in being locked into a wrong choice (ie "If only I had waited one
  678. more frame, I would have gotten the better weapon") because the AI is recomputing this choice
  679. every frame. Picture a tank with a slow reloading missile and a fast low damage gun. This function
  680. will choose the missile, then choose the gun, then want to choose the missile until the gun reloads,
  681. then choose the gun and go back to wanting to choose the missile, etc
  682. */
  683. if (victim == NULL)
  684. return false; // Usually cause victim just got killed. jba.
  685. if( isCurWeaponLocked() )
  686. return TRUE; // I have been forced into choosing a specific weapon, so it is right until someone says otherwise
  687. Bool found = FALSE; // A Ready weapon has been found
  688. Bool foundBackup = FALSE; // An unready, but valid weapon has been found
  689. Real longestRange = 0.0f;
  690. Real bestDamage = 0.0f;
  691. Real longestRangeBackup = 0.0f;
  692. Real bestDamageBackup = 0.0f;
  693. WeaponSlotType currentDecision = PRIMARY_WEAPON;
  694. WeaponSlotType currentDecisionBackup = PRIMARY_WEAPON;
  695. // go backwards, so that in the event of ties, the primary weapon is preferred
  696. for (Int i = WEAPONSLOT_COUNT - 1; i >= PRIMARY_WEAPON ; --i)
  697. {
  698. /*
  699. First: eliminate the weapons that cannot be used.
  700. */
  701. // no weapon in this slot.
  702. if (!m_weapons[i])
  703. continue;
  704. // weapon not allowed to be specified via this command source.
  705. CommandSourceMask okSrcs = m_curWeaponTemplateSet->getNthCommandSourceMask((WeaponSlotType)i);
  706. if ((okSrcs & (1 << cmdSource)) == 0)
  707. continue;
  708. Weapon* weapon = m_weapons[i];
  709. if (weapon == NULL)
  710. continue;
  711. // weapon out of range.
  712. if (!weapon->isWithinAttackRange(obj, victim))
  713. continue;
  714. // weapon out of ammo.
  715. if (weapon->getStatus() == OUT_OF_AMMO && !weapon->getAutoReloadsClip())
  716. continue;
  717. // weapon not allowed to target this kind of thing.
  718. if (!(weapon->getAntiMask() & getVictimAntiMask(victim)))
  719. continue;
  720. if (!weapon->isWithinTargetPitch(obj, victim))
  721. continue;
  722. Real damage = weapon->estimateWeaponDamage(obj, victim);
  723. Real attackRange = weapon->getAttackRange(obj);
  724. Bool weaponIsReady = (weapon->getStatus() == READY_TO_FIRE);
  725. const AIUpdateInterface* ai = obj->getAI();
  726. if (ai && ai->isWeaponSlotOnTurretAndAimingAtTarget((WeaponSlotType)i, victim))
  727. weaponIsReady = false;
  728. // weapon would do no damage against this target.
  729. // exception: 'unresistable' weapons are allowed to do zero damage.
  730. if (damage <= 0.0f && weapon->getDamageType() != DAMAGE_UNRESISTABLE)
  731. continue;
  732. /*
  733. now that we've eliminated the impossible ones, let's decide which
  734. one we will prefer.
  735. */
  736. /*
  737. if a weapon matches the magic "preferred" bits, it's ALWAYS preferred over other weapons, regardless
  738. of actual damage/range. (eg, the Comanche cannon, which is always preferred against infantry, rather
  739. than its antitank missiles.) we accomplish this cheesily, by magnifying the damage/range, and by declaring
  740. the weapon "ready" in a more liberal manner.
  741. */
  742. const KindOfMaskType& preferredAgainst = m_curWeaponTemplateSet->getNthPreferredAgainstMask((WeaponSlotType)i);
  743. if (KINDOFMASK_ANY_SET(preferredAgainst) && victim->isKindOfMulti(preferredAgainst, KINDOFMASK_NONE))
  744. {
  745. const Real HUGE_DAMAGE = 1e10; // wow, that's a lot of damage.
  746. const Real HUGE_RANGE = 1e10;
  747. damage = HUGE_DAMAGE;
  748. attackRange = HUGE_RANGE;
  749. // preferred weapons are also kept if they are merely reloading. (if out of ammo, we can punt.)
  750. weaponIsReady = (weapon->getStatus() != OUT_OF_AMMO);
  751. }
  752. switch (criteria)
  753. {
  754. case PREFER_MOST_DAMAGE:
  755. if( !weaponIsReady )
  756. {
  757. // If this weapon is not ready, the best it can do is qualify as the Best Backup choice
  758. if (damage >= bestDamageBackup)
  759. {
  760. bestDamageBackup = damage;
  761. currentDecisionBackup = (WeaponSlotType)i;
  762. foundBackup = true;
  763. }
  764. }
  765. else
  766. {
  767. if (damage >= bestDamage)
  768. {
  769. bestDamage = damage;
  770. currentDecision = (WeaponSlotType)i;
  771. found = true;
  772. }
  773. }
  774. break;
  775. case PREFER_LONGEST_RANGE:
  776. {
  777. if( !weaponIsReady )
  778. {
  779. if (attackRange > longestRangeBackup)
  780. {
  781. longestRangeBackup = attackRange;
  782. currentDecisionBackup = (WeaponSlotType)i;
  783. foundBackup = true;
  784. }
  785. }
  786. else
  787. {
  788. if (attackRange > longestRange)
  789. {
  790. longestRange = attackRange;
  791. currentDecision = (WeaponSlotType)i;
  792. found = true;
  793. }
  794. }
  795. }
  796. break;
  797. }
  798. }
  799. if ( found )
  800. {
  801. // If we found a good best one, then just use it
  802. m_curWeapon = currentDecision;
  803. }
  804. else if ( foundBackup )
  805. {
  806. // No ready weapon was found, so return the most suitable unready one.
  807. m_curWeapon = currentDecisionBackup;
  808. found = TRUE;
  809. }
  810. else
  811. {
  812. // No weapon at all was found, so we go back to primary.
  813. m_curWeapon = PRIMARY_WEAPON;
  814. }
  815. //DEBUG_LOG(("WeaponSet::chooseBestWeaponForTarget -- changed curweapon to %s\n",getCurWeapon()->getName().str()));
  816. return found;
  817. }
  818. //-------------------------------------------------------------------------------------------------
  819. void WeaponSet::reloadAllAmmo(const Object *obj, Bool now)
  820. {
  821. for( Int i = 0; i < WEAPONSLOT_COUNT; i++ )
  822. {
  823. Weapon* weapon = m_weapons[i];
  824. if (weapon != NULL)
  825. {
  826. if (now)
  827. weapon->loadAmmoNow(obj);
  828. else
  829. weapon->reloadAmmo(obj);
  830. }
  831. }
  832. }
  833. //-------------------------------------------------------------------------------------------------
  834. Bool WeaponSet::isOutOfAmmo() const
  835. {
  836. for( Int i = 0; i < WEAPONSLOT_COUNT; i++ )
  837. {
  838. const Weapon* weapon = m_weapons[i];
  839. if (weapon == NULL)
  840. continue;
  841. if (weapon->getStatus() != OUT_OF_AMMO)
  842. {
  843. return false;
  844. }
  845. }
  846. return true;
  847. }
  848. //-------------------------------------------------------------------------------------------------
  849. const Weapon* WeaponSet::findAmmoPipShowingWeapon() const
  850. {
  851. for( Int i = 0; i < WEAPONSLOT_COUNT; i++ )
  852. {
  853. const Weapon *weapon = m_weapons[ i ];
  854. if (weapon && weapon->isShowsAmmoPips())
  855. {
  856. return weapon;
  857. }
  858. }
  859. return NULL;
  860. }
  861. //-------------------------------------------------------------------------------------------------
  862. Weapon* WeaponSet::findWaypointFollowingCapableWeapon()
  863. {
  864. for( Int i = WEAPONSLOT_COUNT - 1; i >= PRIMARY_WEAPON; i-- )
  865. {
  866. if( m_weapons[i] && m_weapons[i]->isCapableOfFollowingWaypoint() )
  867. {
  868. return m_weapons[i];
  869. }
  870. }
  871. return NULL;
  872. }
  873. //-------------------------------------------------------------------------------------------------
  874. // A special type of command demands that you use this (normally unchooseable) weapon
  875. // until told otherwise.
  876. Bool WeaponSet::setWeaponLock( WeaponSlotType weaponSlot, WeaponLockType lockType )
  877. {
  878. if (lockType == NOT_LOCKED)
  879. {
  880. DEBUG_CRASH(("calling setWeaponLock with NOT_LOCKED, so I am doing nothing... did you mean to use releaseWeaponLock()?\n"));
  881. return false;
  882. }
  883. // Verify the asked for weapon exists , choose it, and then lock it as choosen until unlocked
  884. // the old code was just plain wrong. (look at it in perforce and you'll see...)
  885. if (m_weapons[weaponSlot] != NULL)
  886. {
  887. if( lockType == LOCKED_PERMANENTLY )
  888. {
  889. m_curWeapon = weaponSlot;
  890. m_curWeaponLockedStatus = lockType;
  891. //DEBUG_LOG(("WeaponSet::setWeaponLock permanently -- changed curweapon to %s\n",getCurWeapon()->getName().str()));
  892. }
  893. else if( lockType == LOCKED_TEMPORARILY && m_curWeaponLockedStatus != LOCKED_PERMANENTLY )
  894. {
  895. m_curWeapon = weaponSlot;
  896. m_curWeaponLockedStatus = lockType;
  897. //DEBUG_LOG(("WeaponSet::setWeaponLock temporarily -- changed curweapon to %s\n",getCurWeapon()->getName().str()));
  898. }
  899. return true;
  900. }
  901. DEBUG_CRASH(("setWeaponLock: weapon %d not found (missing an upgrade?)\n", (Int)weaponSlot));
  902. return false;
  903. }
  904. //-------------------------------------------------------------------------------------------------
  905. // Either we have successfully fired a full clip of our special attack, or we have switched
  906. // weaponsets entirely, or any Player issued command besides special attack has been given.
  907. void WeaponSet::releaseWeaponLock(WeaponLockType lockType)
  908. {
  909. if( m_curWeaponLockedStatus == NOT_LOCKED )
  910. return;// Nothing to do
  911. if (lockType == LOCKED_PERMANENTLY)
  912. {
  913. // all locks released.
  914. m_curWeaponLockedStatus = NOT_LOCKED;
  915. }
  916. else if (lockType == LOCKED_TEMPORARILY)
  917. {
  918. // only unlocked if the current lock is temporary.
  919. if (m_curWeaponLockedStatus == LOCKED_TEMPORARILY)
  920. m_curWeaponLockedStatus = NOT_LOCKED;
  921. }
  922. else
  923. {
  924. DEBUG_CRASH(("calling releaseWeaponLock with NOT_LOCKED makes no sense. why did you do this?\n"));
  925. }
  926. }
  927. //-------------------------------------------------------------------------------------------------
  928. Weapon* WeaponSet::getWeaponInWeaponSlot(WeaponSlotType wslot) const
  929. {
  930. return m_weapons[wslot];
  931. }
  932. //-------------------------------------------------------------------------------------------------
  933. //When an AIAttackState is over, it needs to clean up any weapons that might be in leech range mode
  934. //or else those weapons will have unlimited range!
  935. void WeaponSet::clearLeechRangeModeForAllWeapons()
  936. {
  937. for( Int i = 0; i < WEAPONSLOT_COUNT; i++ )
  938. {
  939. Weapon* weapon = m_weapons[ i ];
  940. if( weapon )
  941. {
  942. //Just clear the leech range active flag.
  943. weapon->setLeechRangeActive( FALSE );
  944. }
  945. }
  946. }
  947. //-------------------------------------------------------------------------------------------------
  948. Bool WeaponSet::isSharedReloadTime() const
  949. {
  950. if (m_curWeaponTemplateSet)
  951. return m_curWeaponTemplateSet->isSharedReloadTime();
  952. return false;
  953. }