WeaponSet.cpp 41 KB

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