weapon_base.c 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967
  1. /**@class AbilityRecord
  2. * @brief pair ( action, actionType )
  3. **/
  4. class AbilityRecord
  5. {
  6. int m_action; /// corresponds to Human::actions == RELOAD, MECHANISM, ...
  7. int m_actionType; /// corresponds to Human::actionTypes == CHAMBERING_ONEBULLET_CLOSED, MECHANISM_CLOSED...
  8. void AbilityRecord (int a, int at) { m_action = a; m_actionType = at; }
  9. };
  10. enum WeaponWithAmmoFlags
  11. {
  12. //! Attached magazine will be full and no round will be chambered
  13. NONE = 0,
  14. //! Chambers bullets
  15. CHAMBER = 1,
  16. //! Maybe chambers bullets (sequential rng) example: 1 1 1 0 0 0
  17. CHAMBER_RNG = 2,
  18. //! Maybe chambers bullets (full random) example: 0 1 0 0 1 1
  19. CHAMBER_RNG_SPORADIC = 4,
  20. //! Randomizes the quantity of the bullets in the spawned magazine
  21. QUANTITY_RNG = 8,
  22. //! Fully randomizes the ammo type instead of picking one random for the entire mag (needs to have type as empty string)
  23. AMMO_MAG_RNG = 16,
  24. //! Fully randomizes the ammo type instead of picking one random for all chambers (needs to have type as empty string)
  25. AMMO_CHAMBER_RNG = 32,
  26. //! Instead of randomizing when type is empty, it looks for the one which has the highest capacity
  27. MAX_CAPACITY_MAG = 64,
  28. }
  29. typedef FSMTransition<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> WeaponTransition; /// shorthand
  30. /**@class Weapon_Base
  31. * @brief script base for all weapons
  32. *
  33. * @NOTE: this class is bound to core-config "Weapon_Base" config class
  34. **/
  35. class Weapon_Base extends Weapon
  36. {
  37. //! Full highest capacity magazine + chambered round
  38. const int SAMF_DEFAULT = WeaponWithAmmoFlags.CHAMBER | WeaponWithAmmoFlags.MAX_CAPACITY_MAG;
  39. //! Random bullet quantity + maybe chambered round
  40. const int SAMF_RNG = WeaponWithAmmoFlags.CHAMBER_RNG | WeaponWithAmmoFlags.QUANTITY_RNG;
  41. //! Validation on client side delay to have time properly synchronize attachments needed for check
  42. const float VALIDATE_DELAY = 5.0;
  43. protected const float DEFAULT_DAMAGE_ON_SHOT = 0.05;
  44. protected ref array<ref AbilityRecord> m_abilities = new array<ref AbilityRecord>; /// weapon abilities
  45. protected ref WeaponFSM m_fsm; /// weapon state machine
  46. protected bool m_isJammed = false;
  47. protected bool m_LiftWeapon = false;
  48. protected bool m_BayonetAttached;
  49. protected bool m_ButtstockAttached;
  50. protected bool m_Charged = false;
  51. protected bool m_WeaponOpen = false;
  52. protected bool m_WasIronSight;
  53. protected int m_BurstCount;
  54. protected int m_BayonetAttachmentIdx;
  55. protected int m_ButtstockAttachmentIdx;
  56. protected int m_weaponAnimState = -1; /// animation state the weapon is in, -1 == uninitialized
  57. protected int m_magazineSimpleSelectionIndex = -1;
  58. protected int m_weaponHideBarrelIdx = -1; //index in simpleHiddenSelections cfg array
  59. protected float m_DmgPerShot = 0; //default is set to zero, since C++ solution has been implemented. See 'damageBarrel' and 'barrelArmor' in configs.
  60. protected float m_WeaponLength;
  61. protected float m_WeaponLiftCheckVerticalOffset;
  62. protected float m_ShoulderDistance;
  63. protected vector m_LastLiftPosition;
  64. protected int m_LastLiftHit;
  65. ref array<int> m_bulletSelectionIndex = new array<int>;
  66. ref array<float> m_DOFProperties;
  67. ref array<float> m_ChanceToJam = new array<float>;
  68. protected float m_ChanceToJamSync = 0;
  69. protected ref PropertyModifiers m_PropertyModifierObject;
  70. protected PhxInteractionLayers hit_mask = PhxInteractionLayers.CHARACTER | PhxInteractionLayers.BUILDING | PhxInteractionLayers.DOOR | PhxInteractionLayers.VEHICLE | PhxInteractionLayers.ROADWAY | PhxInteractionLayers.TERRAIN | PhxInteractionLayers.ITEM_SMALL | PhxInteractionLayers.ITEM_LARGE | PhxInteractionLayers.FENCE | PhxInteractionLayers.AI;
  71. protected ref Timer m_DelayedValidationTimer;
  72. private float m_coolDownTime = 0;
  73. void Weapon_Base()
  74. {
  75. //m_DmgPerShot = ConfigGetFloat("damagePerShot");
  76. m_BayonetAttached = false;
  77. m_ButtstockAttached = false;
  78. m_WasIronSight = true; // initially uses ironsights by default
  79. m_BayonetAttachmentIdx = -1;
  80. m_ButtstockAttachmentIdx = -1;
  81. m_BurstCount = 0;
  82. m_DOFProperties = new array<float>;
  83. if (GetGame().IsClient())
  84. {
  85. m_DelayedValidationTimer = new Timer();
  86. }
  87. if ( ConfigIsExisting("simpleHiddenSelections") )
  88. {
  89. TStringArray selectionNames = new TStringArray;
  90. ConfigGetTextArray("simpleHiddenSelections",selectionNames);
  91. m_weaponHideBarrelIdx = selectionNames.Find("hide_barrel");
  92. m_magazineSimpleSelectionIndex = selectionNames.Find("magazine");
  93. int bulletIndex = selectionNames.Find("bullet");
  94. if ( bulletIndex != -1 )
  95. {
  96. m_bulletSelectionIndex.Insert(bulletIndex);
  97. for (int i = 2; i < 100; i++)
  98. {
  99. bulletIndex = selectionNames.Find(string.Format("bullet%1",i));
  100. if (bulletIndex != -1)
  101. {
  102. m_bulletSelectionIndex.Insert(bulletIndex);
  103. }
  104. else
  105. {
  106. break;
  107. }
  108. }
  109. }
  110. }
  111. InitWeaponLength();
  112. InitWeaponLiftCheckVerticalOffset();
  113. InitShoulderDistance();
  114. InitDOFProperties(m_DOFProperties);
  115. if (GetGame().IsServer())
  116. {
  117. InitReliability(m_ChanceToJam);
  118. }
  119. InitStateMachine();
  120. }
  121. void InitStateMachine() { }
  122. override void EEInit()
  123. {
  124. super.EEInit();
  125. if (GetGame().IsServer())
  126. {
  127. GetGame().GetCallQueue( CALL_CATEGORY_GAMEPLAY ).Call( AssembleGun );
  128. }
  129. }
  130. void SetInitialState(WeaponStableState initState)
  131. {
  132. m_fsm.SetInitialState(initState);
  133. SetCharged(!initState.IsDischarged());
  134. SetWeaponOpen(!initState.IsWeaponOpen());
  135. SetGroundAnimFrameIndex(initState.m_animState);
  136. }
  137. bool IsCharged()
  138. {
  139. return m_Charged;
  140. }
  141. void SetCharged(bool value)
  142. {
  143. m_Charged = value;
  144. }
  145. bool IsWeaponOpen()
  146. {
  147. return m_WeaponOpen;
  148. }
  149. void SetWeaponOpen(bool value)
  150. {
  151. m_WeaponOpen = value;
  152. }
  153. override protected float GetWeightSpecialized(bool forceRecalc = false)
  154. {
  155. float baseWeight = GetInventoryAndCargoWeight(forceRecalc);
  156. float ammoWeight;
  157. float ammoDamage;
  158. string bulletTypeName, ammoTypeName;
  159. int muzzleCount = GetMuzzleCount();
  160. #ifdef DEVELOPER
  161. if (WeightDebug.m_VerbosityFlags & WeightDebugType.RECALC_FORCED)
  162. {
  163. WeightDebugData data1 = WeightDebug.GetWeightDebug(this);
  164. data1.SetCalcDetails("TWPN: " + m_ConfigWeight+"(item weight) + " + baseWeight +"(contents weight)" );
  165. }
  166. #endif
  167. for (int muzzleIndex = 0; muzzleIndex < muzzleCount; muzzleIndex++)
  168. {
  169. //chamber weight
  170. if (!IsChamberEmpty(muzzleIndex))
  171. {
  172. ammoTypeName = GetChamberAmmoTypeName(muzzleIndex);
  173. ammoWeight += g_Game.ConfigGetFloat(string.Format("CfgMagazines %1 weight", ammoTypeName));
  174. #ifdef DEVELOPER
  175. if (WeightDebug.m_VerbosityFlags & WeightDebugType.RECALC_FORCED)
  176. {
  177. WeightDebugData data2 = WeightDebug.GetWeightDebug(this);
  178. data2.AddCalcDetails( g_Game.ConfigGetFloat("CfgMagazines " + ammoTypeName + " weight").ToString() +"(chamber weight)");
  179. }
  180. #endif
  181. }
  182. //correctly calculates internal magazine weight based on the ammo type of each bullet
  183. if (HasInternalMagazine(muzzleIndex))
  184. {
  185. #ifdef DEVELOPER
  186. float debugInternalMagWeight;
  187. #endif
  188. int cartridgeCount = GetInternalMagazineCartridgeCount(muzzleIndex);
  189. for (int cartridgeIndex = 0; cartridgeIndex < cartridgeCount; cartridgeIndex++)
  190. {
  191. GetInternalMagazineCartridgeInfo(muzzleIndex, cartridgeIndex, ammoDamage, bulletTypeName);
  192. ammoWeight += Ammunition_Base.GetAmmoWeightByBulletType(bulletTypeName);
  193. #ifdef DEVELOPER
  194. debugInternalMagWeight += g_Game.ConfigGetFloat("CfgMagazines " + ammoTypeName + " weight");
  195. #endif
  196. }
  197. #ifdef DEVELOPER
  198. if (WeightDebug.m_VerbosityFlags & WeightDebugType.RECALC_FORCED)
  199. {
  200. WeightDebugData data3 = WeightDebug.GetWeightDebug(this);
  201. data3.AddCalcDetails(debugInternalMagWeight.ToString()+ "(internal mag weight)");
  202. }
  203. #endif
  204. }
  205. }
  206. return ammoWeight + baseWeight + GetConfigWeightModified();
  207. }
  208. //! override on weapons with some assembly required
  209. void AssembleGun();
  210. bool CanProcessAction(int action, int actionType)
  211. {
  212. return false; // @TODO
  213. }
  214. /**@fn HasActionAbility
  215. * @brief query if weapon supports action and actionType
  216. * @param[in] action \p one of Human.actions (i.e. RELOAD, MECHANISM, ...)
  217. * @param[in] actionType \p one of Human.actionTypes (i.e. CHAMBERING_ONEBULLET_CLOSED, MECHANISM_CLOSED...)
  218. * @return true if weapon supports operation
  219. **/
  220. bool HasActionAbility(int action, int actionType)
  221. {
  222. int count = GetAbilityCount();
  223. for (int i = 0; i < count; ++i)
  224. {
  225. AbilityRecord rec = GetAbility(i);
  226. if (rec.m_action == action && rec.m_actionType == actionType)
  227. return true;
  228. }
  229. return false;
  230. }
  231. /**@fn GetAbilityCount
  232. * @return number of stored abilities
  233. **/
  234. int GetAbilityCount() { return m_abilities.Count(); }
  235. /**@fn GetAbility
  236. * @param[in] index \p index into m_abilities storage
  237. * @return ability record
  238. **/
  239. AbilityRecord GetAbility(int index) { return m_abilities.Get(index); }
  240. /**@fn CanProcessWeaponEvents
  241. * @return true if weapon has running fsm
  242. **/
  243. bool CanProcessWeaponEvents() { return m_fsm && m_fsm.IsRunning(); }
  244. /**@fn GetCurrentState
  245. * @brief returns currently active state
  246. * @return current state the FSM is in (or NULL)
  247. **/
  248. WeaponStateBase GetCurrentState() { return m_fsm.GetCurrentState(); }
  249. /**@fn IsWaitingForActionFinish
  250. * @brief returns true if state machine started playing action/actionType and waits for finish
  251. **/
  252. bool IsWaitingForActionFinish()
  253. {
  254. return CanProcessWeaponEvents() && GetCurrentState().IsWaitingForActionFinish();
  255. }
  256. bool IsIdle()
  257. {
  258. return CanProcessWeaponEvents() && GetCurrentState().IsIdle();
  259. }
  260. /**@fn ProcessWeaponEvent
  261. * @brief weapon's fsm handling of events
  262. * @NOTE: warning: ProcessWeaponEvent can be called only within DayZPlayer::HandleWeapons (or ::CommandHandler)
  263. **/
  264. bool ProcessWeaponEvent(WeaponEventBase e)
  265. {
  266. SyncEventToRemote(e);
  267. // @NOTE: synchronous events not handled by fsm
  268. if (e.GetEventID() == WeaponEventID.SET_NEXT_MUZZLE_MODE)
  269. {
  270. SetNextWeaponMode(GetCurrentMuzzle());
  271. return true;
  272. }
  273. if (m_fsm.ProcessEvent(e) == ProcessEventResult.FSM_OK)
  274. return true;
  275. //if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("FSM refused to process event (no transition): src=" + GetCurrentState().ToString() + " event=" + e.ToString()); }
  276. return false;
  277. }
  278. /**@fn ProcessWeaponAbortEvent
  279. * @NOTE: warning: ProcessWeaponEvent can be called only within DayZPlayer::HandleWeapons (or ::CommandHandler)
  280. **/
  281. bool ProcessWeaponAbortEvent(WeaponEventBase e)
  282. {
  283. SyncEventToRemote(e);
  284. ProcessEventResult aa;
  285. m_fsm.ProcessAbortEvent(e, aa);
  286. return aa == ProcessEventResult.FSM_OK;
  287. }
  288. bool CanChamberBullet(int muzzleIndex, Magazine mag)
  289. {
  290. return CanChamberFromMag(muzzleIndex, mag) && (!IsChamberFull(muzzleIndex) || IsChamberFiredOut(muzzleIndex) || !IsInternalMagazineFull(muzzleIndex));
  291. }
  292. void SetWeaponAnimState(int state)
  293. {
  294. m_weaponAnimState = state;
  295. SetGroundAnimFrameIndex(state);
  296. }
  297. void ResetWeaponAnimState()
  298. {
  299. if (LogManager.IsWeaponLogEnable()) fsmDebugSpam("[wpnfsm] " + Object.GetDebugName(this) + " resetting anim state: " + typename.EnumToString(PistolAnimState, m_weaponAnimState) + " --> " + typename.EnumToString(PistolAnimState, -1));
  300. m_weaponAnimState = -1;
  301. }
  302. int GetWeaponAnimState() { return m_weaponAnimState; }
  303. void EEFired(int muzzleType, int mode, string ammoType)
  304. {
  305. if ( !GetGame().IsDedicatedServer() )
  306. {
  307. ItemBase suppressor = GetAttachedSuppressor();
  308. // Muzzle flash & overheating effects
  309. ItemBase.PlayFireParticles(this, muzzleType, ammoType, this, suppressor, "CfgWeapons" );
  310. IncreaseOverheating(this, ammoType, this, suppressor, "CfgWeapons");
  311. if (suppressor)
  312. {
  313. ItemBase.PlayFireParticles(this, muzzleType, ammoType, suppressor, NULL, "CfgVehicles" );
  314. suppressor.IncreaseOverheating(this, ammoType, this, suppressor, "CfgVehicles");
  315. }
  316. }
  317. //obsolete, replaced by C++ solution!
  318. /*
  319. if (GetGame().IsServer())
  320. {
  321. AddHealth("","Health",-m_DmgPerShot); //damages weapon
  322. if (suppressor)
  323. suppressor.AddHealth("","Health",-m_DmgPerShot); //damages suppressor; TODO add suppressor damage coeficient/parameter (?) to suppressors/weapons (?)
  324. }
  325. */
  326. //JamCheck(muzzleType);
  327. #ifdef DIAG_DEVELOPER
  328. MiscGameplayFunctions.UnlimitedAmmoDebugCheck(this);
  329. #endif
  330. }
  331. bool JamCheck(int muzzleIndex )
  332. {
  333. PlayerBase player = PlayerBase.Cast(GetHierarchyRootPlayer());
  334. if ( player )
  335. {
  336. float rnd = player.GetRandomGeneratorSyncManager().GetRandom01(RandomGeneratorSyncUsage.RGSJam);
  337. //Print("Random Jam - " + rnd);
  338. if (rnd < GetSyncChanceToJam())
  339. return true;
  340. }
  341. return false;
  342. }
  343. void ShowBullet(int muzzleIndex)
  344. {
  345. if ( m_bulletSelectionIndex.Count() > muzzleIndex )
  346. {
  347. SetSimpleHiddenSelectionState(m_bulletSelectionIndex[muzzleIndex],1);
  348. }
  349. else
  350. SelectionBulletShow();
  351. }
  352. void HideBullet(int muzzleIndex)
  353. {
  354. if ( m_bulletSelectionIndex.Count() > muzzleIndex )
  355. {
  356. SetSimpleHiddenSelectionState(m_bulletSelectionIndex[muzzleIndex],0);
  357. }
  358. else
  359. SelectionBulletHide();
  360. }
  361. bool IsJammed() { return m_isJammed; }
  362. bool CanEjectBullet() {return true;}
  363. void SetJammed(bool value) { m_isJammed = value; }
  364. float GetSyncChanceToJam() { return m_ChanceToJamSync; }
  365. float GetChanceToJam()
  366. {
  367. int level = GetHealthLevel();
  368. if (level >= 0 && level < m_ChanceToJam.Count())
  369. return m_ChanceToJam[level];
  370. else
  371. return 0.0;
  372. }
  373. void SyncSelectionState(bool has_bullet, bool has_mag)
  374. {
  375. if (has_bullet)
  376. {
  377. string chamberedAmmoTypeName;
  378. float chamberedAmmoDmg;
  379. if ( GetCartridgeInfo(0, chamberedAmmoDmg, chamberedAmmoTypeName) )
  380. {
  381. EffectBulletShow(0, chamberedAmmoDmg, chamberedAmmoTypeName);
  382. }
  383. //ShowBullet(0);
  384. SelectionBulletShow();
  385. }
  386. else
  387. {
  388. //HideBullet(0);
  389. SelectionBulletHide();
  390. EffectBulletHide(0);
  391. }
  392. if (has_mag)
  393. ShowMagazine();
  394. else
  395. HideMagazine();
  396. }
  397. /*override void EEOnAfterLoad()
  398. {
  399. super.EEOnAfterLoad();
  400. string chamberedAmmoTypeName;
  401. float chamberedAmmoDmg;
  402. if ( GetCartridgeInfo(0, chamberedAmmoDmg, chamberedAmmoTypeName) )
  403. {
  404. EffectBulletShow(0, chamberedAmmoDmg, chamberedAmmoTypeName);
  405. }
  406. }*/
  407. void ForceSyncSelectionState()
  408. {
  409. int nMuzzles = GetMuzzleCount();
  410. for (int i = 0; i < nMuzzles; ++i)
  411. {
  412. if (IsChamberFull(i))
  413. {
  414. ShowBullet(i);
  415. float damage;
  416. string ammoTypeName;
  417. GetCartridgeInfo(i, damage, ammoTypeName);
  418. EffectBulletShow(i, damage, ammoTypeName);
  419. }
  420. else
  421. {
  422. HideBullet(i);
  423. EffectBulletHide(i);
  424. }
  425. Magazine mag = GetMagazine(i);
  426. if (mag)
  427. ShowMagazine();
  428. else
  429. HideMagazine();
  430. }
  431. }
  432. override bool OnStoreLoad(ParamsReadContext ctx, int version)
  433. {
  434. if ( !super.OnStoreLoad(ctx, version) )
  435. return false;
  436. if (version >= 113)
  437. {
  438. int current_muzzle = 0;
  439. if (!ctx.Read(current_muzzle))
  440. {
  441. Error("Weapon.OnStoreLoad " + this + " cannot read current muzzle!");
  442. return false;
  443. }
  444. if (current_muzzle >= GetMuzzleCount() || current_muzzle < 0)
  445. Error("Weapon.OnStoreLoad " + this + " trying to set muzzle index " + current_muzzle + " while it only has " + GetMuzzleCount() + " muzzles!");
  446. else
  447. SetCurrentMuzzle(current_muzzle);
  448. }
  449. if (version >= 105)
  450. {
  451. int mode_count = 0;
  452. if (!ctx.Read(mode_count))
  453. {
  454. Error("Weapon.OnStoreLoad " + this + " cannot read mode count!");
  455. return false;
  456. }
  457. for (int m = 0; m < mode_count; ++m)
  458. {
  459. int mode = 0;
  460. if (!ctx.Read(mode))
  461. {
  462. Error("Weapon.OnStoreLoad " + this + " cannot read mode[" + m + "]");
  463. return false;
  464. }
  465. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " OnStoreLoad - loaded muzzle[" + m + "].mode = " + mode); }
  466. SetCurrentMode(m, mode);
  467. }
  468. }
  469. if ( version >= 106 )
  470. {
  471. if ( !ctx.Read(m_isJammed) )
  472. {
  473. Error("Weapon.OnStoreLoad cannot load jamming state");
  474. return false;
  475. }
  476. }
  477. if (m_fsm)
  478. {
  479. if (!m_fsm.OnStoreLoad(ctx, version))
  480. return false;
  481. WeaponStableState wss = WeaponStableState.Cast(m_fsm.GetCurrentState());
  482. if (wss)
  483. {
  484. SetGroundAnimFrameIndex(wss.m_animState);
  485. }
  486. }
  487. else
  488. {
  489. int dummy = 0;
  490. if (!ctx.Read(dummy))
  491. return false;
  492. }
  493. return true;
  494. }
  495. void SaveCurrentFSMState(ParamsWriteContext ctx)
  496. {
  497. if (m_fsm && m_fsm.IsRunning())
  498. {
  499. if (m_fsm.SaveCurrentFSMState(ctx))
  500. {
  501. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " state saved."); }
  502. }
  503. else
  504. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " state NOT saved.");
  505. }
  506. else
  507. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon.SaveCurrentFSMState: trying to save weapon without FSM (or uninitialized weapon) this=" + this + " type=" + GetType());
  508. }
  509. bool LoadCurrentFSMState(ParamsReadContext ctx, int version)
  510. {
  511. if (m_fsm)
  512. {
  513. if (m_fsm.LoadCurrentFSMState(ctx, version))
  514. {
  515. WeaponStableState state = WeaponStableState.Cast(GetCurrentState());
  516. if (state)
  517. {
  518. SyncSelectionState(state.HasBullet(), state.HasMagazine());
  519. state.SyncAnimState();
  520. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " stable state loaded and synced."); }
  521. return true;
  522. }
  523. else
  524. {
  525. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " unstable/error state loaded."); }
  526. return false;
  527. }
  528. }
  529. else
  530. {
  531. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " did not load.");
  532. return false;
  533. }
  534. }
  535. else
  536. {
  537. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon.LoadCurrentFSMState: trying to load weapon without FSM (or uninitialized weapon) this=" + this + " type=" + GetType());
  538. return false;
  539. }
  540. }
  541. override void AfterStoreLoad()
  542. {
  543. if (m_fsm)
  544. {
  545. int mi = GetCurrentMuzzle();
  546. Magazine mag = GetMagazine(mi);
  547. bool has_mag = mag != null;
  548. bool has_bullet = !IsChamberEmpty(mi);
  549. SyncSelectionState(has_bullet, has_mag);
  550. }
  551. }
  552. override void OnStoreSave(ParamsWriteContext ctx)
  553. {
  554. super.OnStoreSave(ctx);
  555. // current muzzle added in version 113
  556. int current_muzzle = GetCurrentMuzzle();
  557. ctx.Write(current_muzzle);
  558. // fire mode added in version 105
  559. int mode_count = GetMuzzleCount();
  560. ctx.Write(mode_count);
  561. for (int m = 0; m < mode_count; ++m)
  562. ctx.Write(GetCurrentMode(m));
  563. ctx.Write(m_isJammed);
  564. if (m_fsm)
  565. m_fsm.OnStoreSave(ctx);
  566. else
  567. {
  568. int dummy = 0;
  569. ctx.Write(dummy);
  570. }
  571. }
  572. /**@fn GetCurrentStateID
  573. * @brief tries to return identifier of current state
  574. **/
  575. int GetInternalStateID()
  576. {
  577. if (m_fsm)
  578. return m_fsm.GetInternalStateID();
  579. return 0;
  580. }
  581. /**@fn GetCurrentStableStateID
  582. * @brief tries to return identifier of current stable state (or nearest stable state if unstable state is currently running)
  583. **/
  584. int GetCurrentStableStateID()
  585. {
  586. if (m_fsm)
  587. {
  588. return m_fsm.GetCurrentStableStateID();
  589. }
  590. return 0;
  591. }
  592. /**@fn RandomizeFSMState
  593. * @brief With the parameters given, selects a random suitable state for the FSM of the weapon
  594. * @WARNING: Weapon_Base.Synchronize call might be needed, if this method is called while clients are connected
  595. **/
  596. void RandomizeFSMState()
  597. {
  598. if (m_fsm)
  599. {
  600. int mi = GetCurrentMuzzle();
  601. Magazine mag = GetMagazine(mi);
  602. bool has_mag = mag != null;
  603. bool has_bullet = !IsChamberEmpty(mi);
  604. bool has_jam = IsJammed();
  605. array<MuzzleState> muzzleStates = GetMuzzleStates();
  606. m_fsm.RandomizeFSMStateEx(muzzleStates, has_mag, has_jam);
  607. ForceSyncSelectionState();
  608. }
  609. }
  610. //! Helper method for RandomizeFSMState
  611. protected array<MuzzleState> GetMuzzleStates()
  612. {
  613. array<MuzzleState> muzzleStates = new array<MuzzleState>;
  614. int nMuzzles = GetMuzzleCount();
  615. for (int i = 0; i < nMuzzles; ++i)
  616. {
  617. MuzzleState state = MuzzleState.U;
  618. if (IsChamberFiredOut(i))
  619. state = MuzzleState.F;
  620. else if (IsChamberFull(i))
  621. state = MuzzleState.L;
  622. else if (IsChamberEmpty(i))
  623. state = MuzzleState.E;
  624. else
  625. ErrorEx(string.Format("Unable to identify chamber state of muzzle %1", i));
  626. muzzleStates.Insert(state);
  627. }
  628. return muzzleStates;
  629. }
  630. /** \name Weapon With Ammo
  631. * Helpers for spawning ammo/magazine in weapon
  632. * For the flags, either a combination of WeaponWithAmmoFlags can be used
  633. * Or one of the preset 'const int' with 'SAMF_' prefix (SAMF_DEFAULT, SAMF_RNG)
  634. */
  635. //@{
  636. /**@fn CreateWeaponWithAmmo
  637. * @brief Create weapon with ammo
  638. * @param[in] weaponType \p string The weapon to create
  639. * @param[in] magazineType \p string The magazine to attach or ammo to load, passing in empty string will select random
  640. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  641. * @return The created weapon
  642. **/
  643. static Weapon_Base CreateWeaponWithAmmo( string weaponType, string magazineType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  644. {
  645. Weapon_Base wpn = Weapon_Base.Cast(GetGame().CreateObjectEx( weaponType, vector.Zero, ECE_PLACE_ON_SURFACE ));
  646. if ( !wpn )
  647. {
  648. ErrorEx(string.Format("%1 does not exist or is not a weapon.", weaponType));
  649. return null;
  650. }
  651. wpn.SpawnAmmo(magazineType, flags);
  652. return wpn;
  653. }
  654. /**@fn SpawnAmmo
  655. * @brief General method trying to attch magazine, fill inner magazine and fill chamber
  656. * @param[in] magazineType \p string The magazine to attach or ammo to load, passing in empty string will select random
  657. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  658. * @return whether anything was spawned or done
  659. **/
  660. bool SpawnAmmo( string magazineType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  661. {
  662. // Attempt internal mag
  663. if ( HasInternalMagazine(-1) && FillInnerMagazine(magazineType, flags) )
  664. return true;
  665. // Attempt mag attachment
  666. if ( GetMagazineTypeCount(0) > 0 && SpawnAttachedMagazine(magazineType, flags) )
  667. return true;
  668. // Attempt chamber
  669. if ( FillChamber(magazineType, flags) )
  670. return true;
  671. return false;
  672. }
  673. /**@fn SpawnAttachedMagazine
  674. * @brief Try to spawn and attach a magazine
  675. * @param[in] magazineType \p string The magazine to attach, passing in empty string will select random
  676. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  677. * @return The created magazine or null
  678. **/
  679. Magazine SpawnAttachedMagazine( string magazineType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  680. {
  681. // Check if the gun has any magazines registered in config
  682. if ( GetMagazineTypeCount(0) == 0 )
  683. {
  684. ErrorEx(string.Format("No 'magazines' config entry for %1.", this));
  685. return null;
  686. }
  687. // Randomize when no specific one is given
  688. if ( magazineType == "" )
  689. {
  690. if ( flags & WeaponWithAmmoFlags.MAX_CAPACITY_MAG)
  691. magazineType = GetMaxMagazineTypeName(0);
  692. else
  693. magazineType = GetRandomMagazineTypeName(0);
  694. }
  695. EntityAI magAI = GetInventory().CreateAttachment(magazineType);
  696. if (!magAI)
  697. {
  698. ErrorEx(string.Format("Failed to create and attach %1 to %2", GetDebugName(magAI), this));
  699. return null;
  700. }
  701. Magazine mag;
  702. if (!CastTo(mag, magAI))
  703. {
  704. ErrorEx(string.Format("Expected magazine, created: %1", GetDebugName(magAI)));
  705. return null;
  706. }
  707. // Decide random quantity when enabled
  708. if (flags & WeaponWithAmmoFlags.QUANTITY_RNG)
  709. mag.ServerSetAmmoCount(Math.RandomIntInclusive(0, mag.GetAmmoMax()));
  710. // Fill chamber when flagged
  711. bool chamberRng = (flags & WeaponWithAmmoFlags.CHAMBER_RNG);
  712. bool chamber = (flags & WeaponWithAmmoFlags.CHAMBER) || chamberRng;
  713. if (chamber || chamberRng)
  714. {
  715. FillChamber(magazineType, flags);
  716. }
  717. // FSM cares about magazine state
  718. RandomizeFSMState();
  719. Synchronize();
  720. return mag;
  721. }
  722. /**@fn FillInnerMagazine
  723. * @brief Try to fill the inner magazine
  724. * @param[in] ammoType \p string The ammo to load, passing in empty string will select random
  725. * @note It is best to fill in the actual 'ammo', as in the ones usually prefixed by 'Bullet_', to skip searching for it
  726. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  727. * @return Whether any ammo was added to the gun or not
  728. **/
  729. bool FillInnerMagazine( string ammoType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  730. {
  731. // Don't try to fill it when there are none
  732. if (!HasInternalMagazine(-1))
  733. return false;
  734. // Make sure the given ammoType is actually useable
  735. if (ammoType != "")
  736. {
  737. if (!AmmoTypesAPI.MagazineTypeToAmmoType(ammoType, ammoType))
  738. return false;
  739. }
  740. bool didSomething = false;
  741. int muzzCount = GetMuzzleCount();
  742. bool ammoRng = ammoType == "";
  743. bool ammoFullRng = ammoRng && (flags & WeaponWithAmmoFlags.AMMO_MAG_RNG);
  744. // No full RNG flag, so pick one random and use only this one
  745. if (ammoRng && !ammoFullRng)
  746. ammoType = GetRandomChamberableAmmoTypeName(0);
  747. // Fill the internal magazine
  748. for (int i = 0; i < muzzCount; ++i)
  749. {
  750. int ammoCount = GetInternalMagazineMaxCartridgeCount(i);
  751. // Decide random quantity when enabled
  752. if ( flags & WeaponWithAmmoFlags.QUANTITY_RNG )
  753. ammoCount = Math.RandomIntInclusive(0, ammoCount);
  754. // Only do the things when there is actually ammo to fill
  755. if (ammoCount > 0)
  756. {
  757. // Push in the cartridges
  758. for (int j = 0; j < ammoCount; ++j)
  759. {
  760. // Full random, decide a new one for every cartridge
  761. if ( ammoFullRng )
  762. ammoType = GetRandomChamberableAmmoTypeName(i);
  763. PushCartridgeToInternalMagazine(i, 0, ammoType);
  764. didSomething = true;
  765. }
  766. }
  767. }
  768. // Call the chamber method if asked for
  769. bool chamber = (flags & WeaponWithAmmoFlags.CHAMBER) || (flags & WeaponWithAmmoFlags.CHAMBER_RNG);
  770. if (chamber && FillChamber(ammoType, flags))
  771. {
  772. didSomething = true;
  773. }
  774. // Does not need any FSM fixing, FSM does not care about inner magazines
  775. return didSomething;
  776. }
  777. /**@fn FillChamber
  778. * @brief Try to fill the chamber
  779. * @param[in] ammoType \p string The ammo to load, passing in empty string will select random
  780. * @note It is best to fill in the actual 'ammo', as in the ones usually prefixed by 'Bullet_', to skip searching for it
  781. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  782. * @return Whether any chamber was filled
  783. **/
  784. bool FillChamber( string ammoType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  785. {
  786. // Quickly check if there are any chambers we can fill
  787. int muzzCount = GetMuzzleCount();
  788. bool anyEmpty = false;
  789. for (int m = 0; m < muzzCount; ++m)
  790. {
  791. if (IsChamberEmpty(m))
  792. {
  793. anyEmpty = true;
  794. break;
  795. }
  796. }
  797. if (!anyEmpty)
  798. return false;
  799. // Make sure the given ammoType is actually useable
  800. if (ammoType != "")
  801. if (!AmmoTypesAPI.MagazineTypeToAmmoType(ammoType, ammoType))
  802. return false;
  803. // Just so we don't '&' wastefully in a loop
  804. bool didSomething = false;
  805. bool chamberFullRng = (flags & WeaponWithAmmoFlags.CHAMBER_RNG_SPORADIC);
  806. bool chamberRng = (flags & WeaponWithAmmoFlags.CHAMBER_RNG);
  807. bool chamber = (flags & WeaponWithAmmoFlags.CHAMBER);
  808. if (chamber || chamberRng || chamberFullRng)
  809. {
  810. int amountToChamber = muzzCount;
  811. // No need to do this for full rng, as that will roll for every muzzle
  812. if (chamberRng)
  813. amountToChamber = Math.RandomIntInclusive(0, muzzCount);
  814. bool chamberAmmoRng = (ammoType == "");
  815. bool chamberAmmoFullRng = chamberAmmoRng && (flags & WeaponWithAmmoFlags.AMMO_CHAMBER_RNG);
  816. // No full RNG flag, so pick one random and use only this one
  817. if (chamberAmmoRng && !chamberAmmoFullRng)
  818. ammoType = GetRandomChamberableAmmoTypeName(0);
  819. for (int i = 0; i < muzzCount; ++i)
  820. {
  821. // Skip when there's already something in the chamber
  822. if (!IsChamberEmpty(i))
  823. continue;
  824. // Roll the rng when enabled
  825. if (chamberFullRng)
  826. chamber = Math.RandomIntInclusive(0, 1);
  827. // We chambering
  828. if (chamber)
  829. {
  830. // Full random, decide a new one for every muzzle
  831. if ( chamberAmmoFullRng )
  832. ammoType = GetRandomChamberableAmmoTypeName(i);
  833. // Push it
  834. PushCartridgeToChamber(i, 0, ammoType);
  835. didSomething = true;
  836. // Stop chambering when we hit the desired amount
  837. --amountToChamber;
  838. if (amountToChamber <= 0)
  839. break;
  840. }
  841. }
  842. }
  843. // Only fix the FSM and Synchronize when absolutely needed
  844. if (!didSomething)
  845. return false;
  846. // FSM cares about chamber state
  847. RandomizeFSMState();
  848. Synchronize();
  849. return true;
  850. }
  851. //@}
  852. /**
  853. * @brief Returns number of slots for attachments corrected for weapons
  854. **/
  855. override int GetSlotsCountCorrect()
  856. {
  857. int ac = GetInventory().AttachmentCount();
  858. int sc = GetInventory().GetAttachmentSlotsCount() + GetMuzzleCount();
  859. if (ac > sc) sc = ac; // fix of some weapons which has 1 attachments but 0 slots...
  860. return sc;
  861. };
  862. PropertyModifiers GetPropertyModifierObject()
  863. {
  864. if (!m_PropertyModifierObject)
  865. {
  866. m_PropertyModifierObject = new PropertyModifiers(this);
  867. }
  868. return m_PropertyModifierObject;
  869. }
  870. void OnFire(int muzzle_index)
  871. {
  872. /*
  873. array<Man> players();
  874. GetGame().GetPlayers(players);
  875. Man root = GetHierarchyRootPlayer();
  876. if (!root)
  877. {
  878. return;
  879. }
  880. vector safePosition = root.GetPosition() + (root.GetDirection() * "0 1 0" * 3.0);
  881. Man other = null;
  882. foreach (auto player : players)
  883. {
  884. if (player != GetHierarchyRootPlayer())
  885. {
  886. player.SetPosition(safePosition);
  887. }
  888. }
  889. */
  890. m_BurstCount++;
  891. }
  892. void OnFireModeChange(int fireMode)
  893. {
  894. if ( !GetGame().IsDedicatedServer() )
  895. {
  896. EffectSound eff;
  897. if ( fireMode == 0 )
  898. eff = SEffectManager.PlaySound("Fire_Mode_Switch_Marked_Click_SoundSet", GetPosition());
  899. else
  900. eff = SEffectManager.PlaySound("Fire_Mode_Switch_Simple_Click_SoundSet", GetPosition());
  901. eff.SetAutodestroy(true);
  902. }
  903. ResetBurstCount();
  904. }
  905. void DelayedValidateAndRepair()
  906. {
  907. if (m_DelayedValidationTimer)
  908. {
  909. m_DelayedValidationTimer.Run(VALIDATE_DELAY , this, "ValidateAndRepair");
  910. }
  911. else
  912. {
  913. Error("[wpn] Weapon_Base::DelayedValidateAndRepair m_DelayedValidationTimer not initialized.");
  914. ValidateAndRepair();
  915. }
  916. }
  917. void ValidateAndRepair()
  918. {
  919. if ( m_fsm )
  920. m_fsm.ValidateAndRepair();
  921. }
  922. override void OnInventoryEnter(Man player)
  923. {
  924. m_PropertyModifierObject = null;
  925. if (GetGame().IsServer())
  926. {
  927. // The server knows about all of its attachments
  928. ValidateAndRepair();
  929. }
  930. else
  931. {
  932. // The client doesn't know it has attachments yet... give it a moment
  933. DelayedValidateAndRepair();
  934. }
  935. super.OnInventoryEnter(player);
  936. }
  937. override void OnInventoryExit(Man player)
  938. {
  939. m_PropertyModifierObject = null;
  940. super.OnInventoryExit(player);
  941. }
  942. override void EEItemAttached(EntityAI item, string slot_name)
  943. {
  944. super.EEItemAttached(item, slot_name);
  945. GetPropertyModifierObject().UpdateModifiers();
  946. }
  947. override void EEItemDetached(EntityAI item, string slot_name)
  948. {
  949. super.EEItemDetached(item, slot_name);
  950. GetPropertyModifierObject().UpdateModifiers();
  951. }
  952. override void EEItemLocationChanged(notnull InventoryLocation oldLoc, notnull InventoryLocation newLoc)
  953. {
  954. super.EEItemLocationChanged(oldLoc, newLoc);
  955. if (newLoc.GetType() == InventoryLocationType.HANDS)
  956. {
  957. PlayerBase player;
  958. if (newLoc.GetParent() && PlayerBase.CastTo(player, newLoc.GetParent()))
  959. {
  960. HumanCommandMove cm = player.GetCommand_Move();
  961. if (cm)
  962. {
  963. cm.SetMeleeBlock(false);
  964. }
  965. }
  966. }
  967. }
  968. override void OnItemLocationChanged(EntityAI old_owner, EntityAI new_owner)
  969. {
  970. /*
  971. // TODO(kumarjac): Solve this in code instead, OnItemLocationChanged is called too late.
  972. // Maybe extend an option for items to specify what attachments they must
  973. // be synchronized with? Moving to 'DelayedValidateAndRepair' works for now.
  974. int muzzles = GetMuzzleCount();
  975. for (int muzzleIdx = 0; muzzleIdx < muzzles; muzzleIdx++)
  976. {
  977. Magazine mag = GetMagazine(muzzleIdx);
  978. Print(mag);
  979. if (!mag)
  980. continue;
  981. Print(mag.GetInventory());
  982. if (!mag.GetInventory())
  983. continue;
  984. InventoryLocation invLoc = new InventoryLocation();
  985. mag.GetInventory().GetCurrentInventoryLocation(invLoc);
  986. GetGame().AddInventoryJunctureEx(null, this, invLoc, true, 1000);
  987. }
  988. */
  989. super.OnItemLocationChanged(old_owner,new_owner);
  990. // "resets" optics memory on optics
  991. PlayerBase player;
  992. if (PlayerBase.CastTo(player,old_owner))
  993. {
  994. player.SetReturnToOptics(false);
  995. //optics item state reset
  996. ItemOptics optics;
  997. if (Class.CastTo(optics,GetAttachedOptics()))
  998. {
  999. player.SwitchOptics(optics,false);
  1000. }
  1001. }
  1002. if (old_owner != new_owner && PlayerBase.Cast(new_owner))
  1003. SetWasIronSight(true); // reset ironsight/optic default
  1004. HideWeaponBarrel(false);
  1005. }
  1006. override bool CanReleaseAttachment(EntityAI attachment)
  1007. {
  1008. if ( !super.CanReleaseAttachment( attachment ) )
  1009. return false;
  1010. Magazine mag = Magazine.Cast(attachment);
  1011. if (mag)
  1012. {
  1013. PlayerBase player = PlayerBase.Cast( GetHierarchyRootPlayer() );
  1014. if ( player )
  1015. {
  1016. if ( player.GetItemInHands() == this )
  1017. return true;
  1018. }
  1019. return false;
  1020. }
  1021. return true;
  1022. }
  1023. override bool CanRemoveFromHands(EntityAI parent)
  1024. {
  1025. if (IsIdle())
  1026. {
  1027. return true;
  1028. }
  1029. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " not in stable state=" + GetCurrentState().Type()); }
  1030. return false; // do not allow removal of weapon while weapon is busy
  1031. }
  1032. bool IsRemoteWeapon()
  1033. {
  1034. InventoryLocation il = new InventoryLocation;
  1035. if (GetInventory().GetCurrentInventoryLocation(il))
  1036. {
  1037. EntityAI parent = il.GetParent();
  1038. DayZPlayer dayzp = DayZPlayer.Cast(parent);
  1039. if (il.GetType() == InventoryLocationType.HANDS && dayzp)
  1040. {
  1041. bool remote = dayzp.GetInstanceType() == DayZPlayerInstanceType.INSTANCETYPE_REMOTE;
  1042. return remote;
  1043. }
  1044. }
  1045. return true;
  1046. }
  1047. void SyncEventToRemote(WeaponEventBase e)
  1048. {
  1049. DayZPlayer p = DayZPlayer.Cast(GetHierarchyParent());
  1050. if (p && p.GetInstanceType() == DayZPlayerInstanceType.INSTANCETYPE_SERVER)
  1051. {
  1052. ScriptRemoteInputUserData ctx = new ScriptRemoteInputUserData();
  1053. ctx.Write(INPUT_UDT_WEAPON_REMOTE_EVENT);
  1054. e.WriteToContext(ctx);
  1055. if (LogManager.IsWeaponLogEnable())
  1056. wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " send 2 remote: sending e=" + e + " id=" + e.GetEventID() + " p=" + e.m_player + " m=" + e.m_magazine);
  1057. p.StoreInputForRemotes(ctx);
  1058. }
  1059. }
  1060. RecoilBase SpawnRecoilObject()
  1061. {
  1062. return new DefaultRecoil(this);
  1063. }
  1064. int GetWeaponSpecificCommand(int weaponAction, int subCommand)
  1065. {
  1066. return subCommand;
  1067. }
  1068. bool CanFire()
  1069. {
  1070. if (!IsChamberEmpty(GetCurrentMuzzle()) && !IsChamberFiredOut(GetCurrentMuzzle()) && !IsJammed() && !m_LiftWeapon && !IsDamageDestroyed())
  1071. return true;
  1072. return false;
  1073. }
  1074. bool CanEnterIronsights()
  1075. {
  1076. ItemOptics optic = GetAttachedOptics();
  1077. if (!optic)
  1078. return true;
  1079. return optic.HasWeaponIronsightsOverride();
  1080. }
  1081. //! Initializes DOF properties for weapon's ironsight/optics cameras
  1082. bool InitDOFProperties(out array<float> temp_array)
  1083. {
  1084. if (GetGame().ConfigIsExisting("cfgWeapons " + GetType() + " PPDOFProperties"))
  1085. {
  1086. GetGame().ConfigGetFloatArray("cfgWeapons " + GetType() + " PPDOFProperties", temp_array);
  1087. return true;
  1088. }
  1089. return false;
  1090. }
  1091. bool InitReliability(out array<float> reliability_array)
  1092. {
  1093. if (GetGame().ConfigIsExisting("cfgWeapons " + GetType() + " Reliability ChanceToJam"))
  1094. {
  1095. GetGame().ConfigGetFloatArray("cfgWeapons " + GetType() + " Reliability ChanceToJam", reliability_array);
  1096. return true;
  1097. }
  1098. return false;
  1099. }
  1100. //!gets weapon length from config for weaponlift raycast
  1101. bool InitWeaponLength()
  1102. {
  1103. if (ConfigIsExisting("WeaponLength"))
  1104. {
  1105. m_WeaponLength = ConfigGetFloat("WeaponLength");
  1106. return true;
  1107. }
  1108. m_WeaponLength = 0.8; //default value if not set in config; should not be zero
  1109. return false;
  1110. }
  1111. //!gets weapon vertical offset from config for weaponlift raycast
  1112. bool InitWeaponLiftCheckVerticalOffset()
  1113. {
  1114. if (ConfigIsExisting("WeaponLiftCheckVerticalOffset"))
  1115. {
  1116. m_WeaponLiftCheckVerticalOffset = ConfigGetFloat("WeaponLiftCheckVerticalOffset");
  1117. return true;
  1118. }
  1119. m_WeaponLiftCheckVerticalOffset = 0.0; //no offset by default
  1120. return false;
  1121. }
  1122. //!gets approximate weapon distance from shoulder from config
  1123. protected bool InitShoulderDistance()
  1124. {
  1125. if (ConfigIsExisting("ShoulderDistance"))
  1126. {
  1127. m_ShoulderDistance = ConfigGetFloat("ShoulderDistance");
  1128. return true;
  1129. }
  1130. m_ShoulderDistance = 0;
  1131. return false;
  1132. }
  1133. ref array<float> GetWeaponDOF()
  1134. {
  1135. return m_DOFProperties;
  1136. }
  1137. bool GetWasIronSight()
  1138. {
  1139. return m_WasIronSight;
  1140. }
  1141. // used to remember last used ironsight/optic state
  1142. void SetWasIronSight(bool state)
  1143. {
  1144. m_WasIronSight = state;
  1145. }
  1146. // lifting weapon on obstcles
  1147. bool LiftWeaponCheck(PlayerBase player)
  1148. {
  1149. int idx;
  1150. float distance;
  1151. float hit_fraction;
  1152. vector start, end;
  1153. vector direction;
  1154. vector hit_pos, hit_normal;
  1155. Object obj;
  1156. bool wasLift = m_LiftWeapon;
  1157. vector lastLiftPosition = m_LastLiftPosition;
  1158. m_LiftWeapon = false;
  1159. // not a gun, no weap.raise for now
  1160. if ( HasSelection("Usti hlavne") )
  1161. return false;
  1162. if (!player)
  1163. {
  1164. Print("Error: No weapon owner, returning");
  1165. return false;
  1166. }
  1167. // weapon not raised
  1168. HumanMovementState movementState = new HumanMovementState();
  1169. player.GetMovementState(movementState);
  1170. if (!movementState.IsRaised())
  1171. return false;
  1172. // suppress raising of weapon during melee attack preventing state inconsistency
  1173. if (movementState.m_CommandTypeId == DayZPlayerConstants.COMMANDID_MELEE || movementState.m_CommandTypeId == DayZPlayerConstants.COMMANDID_MELEE2)
  1174. {
  1175. return false;
  1176. }
  1177. // If possible use aiming angles instead as these will work consistently
  1178. // and independently of any cameras, etc.
  1179. HumanCommandWeapons hcw = player.GetCommandModifier_Weapons();
  1180. if (hcw)
  1181. {
  1182. vector yawPitchRoll = Vector(
  1183. hcw.GetBaseAimingAngleLR() + player.GetOrientation()[0],
  1184. hcw.GetBaseAimingAngleUD(),
  1185. 0.0);
  1186. float xAimHandsOffset = hcw.GetAimingHandsOffsetLR();
  1187. float yAimHandsOffset = hcw.GetAimingHandsOffsetUD();
  1188. yawPitchRoll[0] = yawPitchRoll[0] + xAimHandsOffset;
  1189. yawPitchRoll[1] = Math.Clamp( yawPitchRoll[1] + yAimHandsOffset, DayZPlayerCamera1stPerson.CONST_UD_MIN, DayZPlayerCamera1stPerson.CONST_UD_MAX );
  1190. direction = yawPitchRoll.AnglesToVector();
  1191. }
  1192. else // Fallback to previously implemented logic
  1193. {
  1194. // freelook raycast
  1195. if (player.GetInputController().CameraIsFreeLook())
  1196. {
  1197. if (player.m_DirectionToCursor != vector.Zero)
  1198. {
  1199. direction = player.m_DirectionToCursor;
  1200. }
  1201. // if player raises weapon in freelook
  1202. else
  1203. {
  1204. direction = MiscGameplayFunctions.GetHeadingVector(player);
  1205. }
  1206. }
  1207. else
  1208. {
  1209. direction = GetGame().GetCurrentCameraDirection(); // exception for freelook. Much better this way!
  1210. }
  1211. }
  1212. idx = player.GetBoneIndexByName("Neck"); //RightHandIndex1
  1213. if ( idx == -1 )
  1214. { start = player.GetPosition()[1] + 1.5; }
  1215. else
  1216. { start = player.GetBonePositionWS(idx); }
  1217. // Updated weapon lift detection code prototype
  1218. {
  1219. // 0: construct stable trasformation matrix that
  1220. // approximately aligns with the weapon transform
  1221. // without actually using the weapon as reference
  1222. // (as the weapon can be moved unpredictably by anims)
  1223. vector resTM[4];
  1224. resTM[0] = Vector(direction[0], 0, direction[2]).Normalized();
  1225. resTM[0] = vector.RotateAroundZeroDeg(resTM[0], vector.Up, 90);
  1226. resTM[2] = direction;
  1227. resTM[1] = resTM[2] * resTM[0];
  1228. resTM[3] = start;
  1229. // Approximate the roll of leaning
  1230. HumanMovementState hms = new HumanMovementState();
  1231. player.GetMovementState(hms);
  1232. float leanAngle = hms.m_fLeaning * 35;
  1233. vector rotTM[3];
  1234. Math3D.YawPitchRollMatrix( Vector(xAimHandsOffset , yAimHandsOffset, leanAngle), rotTM );
  1235. Math3D.MatrixMultiply3(resTM, rotTM, resTM);
  1236. // Draw relative TM diagnostic
  1237. #ifdef DIAG_DEVELOPER
  1238. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_LIFT_DEBUG))
  1239. {
  1240. Shape.CreateArrow(resTM[3], resTM[3] + resTM[0], 0.05, COLOR_RED, ShapeFlags.ONCE);
  1241. Shape.CreateArrow(resTM[3], resTM[3] + resTM[1], 0.05, COLOR_GREEN, ShapeFlags.ONCE);
  1242. Shape.CreateArrow(resTM[3], resTM[3] + resTM[2], 0.05, COLOR_BLUE, ShapeFlags.ONCE);
  1243. }
  1244. #endif
  1245. // 1: pick from predefined offset relative to
  1246. // the previously constructed transform
  1247. float udAngle = Math.Asin(direction[1]) * Math.RAD2DEG;
  1248. // offsets are [right, up, forward]
  1249. // values are based on what felt right after iterating
  1250. vector offsets[] =
  1251. {
  1252. "0.11 0.17 0.0", // offset while aiming down
  1253. "0.12 0.05 0.0", // offset while aiming forward
  1254. "0.112 0.03 0.0" // offset while aiming up
  1255. };
  1256. const int lastIndex = 2; // length of offsets - 1
  1257. // <0,1> range of aiming
  1258. float a = Math.Clamp(Math.InverseLerp(DayZPlayerCamera1stPerson.CONST_UD_MIN, DayZPlayerCamera1stPerson.CONST_UD_MAX, udAngle), 0, 0.9999);
  1259. int lo = a * lastIndex;
  1260. int hi = Math.Clamp(lo+1, 0, lastIndex);
  1261. // remap to current lo-hi range
  1262. float t = Math.Clamp(a * lastIndex - lo, 0, 1);
  1263. vector offset = vector.Lerp(offsets[lo], offsets[hi], t);
  1264. // offsets are [right, up forward]
  1265. // additional offsets added to previous offsets per stance
  1266. vector stanceOffsets[] =
  1267. {
  1268. "0 -0.015 0", // erect
  1269. "0 0.03 0", // crouch
  1270. "0 -0.04 0",// prone
  1271. };
  1272. // 2. pick from predefined offsets based on stance,
  1273. // allows to even further approximate the alignment
  1274. int stanceOffsetIndex = hms.m_iStanceIdx;
  1275. if (stanceOffsetIndex >= DayZPlayerConstants.STANCEIDX_PRONE)
  1276. stanceOffsetIndex -= DayZPlayerConstants.STANCEIDX_RAISED;
  1277. stanceOffsetIndex -= DayZPlayerConstants.STANCEIDX_ERECT;
  1278. offset += stanceOffsets[stanceOffsetIndex];
  1279. // if any additional height offset is defined, apply it
  1280. if (m_WeaponLiftCheckVerticalOffset != 0)
  1281. {
  1282. offset[1] = offset[1] + m_WeaponLiftCheckVerticalOffset;
  1283. }
  1284. //
  1285. offset = offset.InvMultiply3(rotTM);
  1286. // 3. use the offset as the start position.
  1287. // it will not be perfect, but it should reflect
  1288. // the actual weapon transform more accurately
  1289. start = offset.Multiply4(resTM);
  1290. }
  1291. distance = m_WeaponLength + GetEffectiveAttachmentLength();
  1292. vector weaponStart = start + (m_ShoulderDistance * direction);
  1293. vector weaponEnd = weaponStart + (distance * direction);
  1294. // Draw diagnostics: Script -> Weapon -> Weapon Lift
  1295. #ifdef DIAG_DEVELOPER
  1296. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_LIFT_DEBUG))
  1297. {
  1298. vector diagNoAttachEnd = weaponStart + (m_WeaponLength * direction);
  1299. int diagPtsShpFlgs = ShapeFlags.ONCE | ShapeFlags.NOOUTLINE;
  1300. float diagPtsRadius = 0.025;
  1301. Shape.CreateSphere(COLOR_GREEN, diagPtsShpFlgs, weaponStart, diagPtsRadius);
  1302. Shape.CreateSphere(COLOR_YELLOW, diagPtsShpFlgs, diagNoAttachEnd, diagPtsRadius);
  1303. Shape.CreateSphere(COLOR_BLUE, diagPtsShpFlgs, weaponEnd, diagPtsRadius);
  1304. }
  1305. #endif
  1306. // For the physical cast, extend the distance by x%
  1307. // to allow for smoother transition handling in some cases
  1308. end = weaponEnd + ((0.1 * distance) * direction);
  1309. // Prepare raycast params and perform the cast in fire geo
  1310. RaycastRVParams rayParm = new RaycastRVParams(start, end, player, 0.02);
  1311. rayParm.flags = CollisionFlags.ALLOBJECTS;
  1312. rayParm.type = ObjIntersect.Fire;
  1313. #ifdef DIAG_DEVELOPER
  1314. DbgUI.BeginCleanupScope();
  1315. #endif
  1316. array<ref RaycastRVResult> results = {};
  1317. if (!DayZPhysics.RaycastRVProxy(rayParm, results) || results.Count() == 0)
  1318. {
  1319. hit_pos = vector.Zero;
  1320. hit_fraction = 0;
  1321. }
  1322. else
  1323. {
  1324. RaycastRVResult res = results[0];
  1325. #ifdef DIAG_DEVELOPER // isect diag
  1326. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_LIFT_DEBUG) == 2)
  1327. {
  1328. DbgUI.Begin("Weapon Lift Diag");
  1329. {
  1330. if (res.surface)
  1331. {
  1332. DbgUI.Text("Intersection data:");
  1333. DbgUI.Text(" Name: " + res.surface.GetName());
  1334. DbgUI.Text(" EntryName: " + res.surface.GetEntryName());
  1335. DbgUI.Text(" SurfaceType: " + res.surface.GetSurfaceType());
  1336. DbgUI.Text(" IsPassThrough: " + res.surface.IsPassthrough());
  1337. DbgUI.Text(" IsSolid: " + res.surface.IsSolid());
  1338. }
  1339. else
  1340. {
  1341. DbgUI.Text("Intersection with no surface");
  1342. }
  1343. }
  1344. DbgUI.End();
  1345. }
  1346. #endif // !isect diag
  1347. if (LiftWeaponRaycastResultCheck(res))
  1348. {
  1349. hit_pos = res.pos;
  1350. float len0 = (hit_pos - start).Length();
  1351. float len1 = (end - start).Length();
  1352. if (len0 <= 0 || len1 <= 0)
  1353. {
  1354. hit_fraction = 1;
  1355. }
  1356. else
  1357. {
  1358. hit_fraction = len0 / len1;
  1359. }
  1360. }
  1361. else
  1362. {
  1363. hit_pos = vector.Zero;
  1364. hit_fraction = 0;
  1365. }
  1366. }
  1367. #ifdef DIAG_DEVELOPER
  1368. DbgUI.EndCleanupScope();
  1369. #endif
  1370. // Draw diagnostics: Script -> Weapon -> Weapon Lift
  1371. #ifdef DIAG_DEVELOPER
  1372. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_LIFT_DEBUG))
  1373. {
  1374. const vector epsilon = "0 0.0002 0"; // to overcome excessive z-fighting for diag
  1375. if (lastLiftPosition!=vector.Zero)
  1376. {
  1377. Shape.CreateArrow(start-epsilon, lastLiftPosition-epsilon, 0.05, COLOR_YELLOW, ShapeFlags.ONCE);
  1378. }
  1379. Shape.CreateArrow(start, weaponEnd, 0.05, COLOR_WHITE, ShapeFlags.ONCE | ShapeFlags.NOZBUFFER );
  1380. if (hit_fraction != 0)
  1381. {
  1382. Shape.CreateArrow(start+epsilon, hit_pos+epsilon, 0.05, COLOR_RED, ShapeFlags.ONCE);
  1383. }
  1384. }
  1385. #endif
  1386. // Start by assuming that we want to retain state
  1387. bool wantsLift = wasLift;
  1388. // Sq. distance of weapon movement required to trigger lift (in)
  1389. const float inThreshold = 0.002;
  1390. // Sq. distance of weapon movement required to trigger lift (out)
  1391. const float outThreshold = 0.003;
  1392. const float noIsctOutThreshold = 0.01;
  1393. // Max num of ticks with no hit for which hysteresis will persist
  1394. // value chosen by iteration, should be approx 0.333s
  1395. const int maxNumMissedTicks = 10;
  1396. // Min angle in degrees change from last lift to stop lifting
  1397. // Base threshold of 0.75 degrees + 0.6 degrees per meter of weapon length
  1398. float angleThreshold = 0.75 + Math.Clamp( m_WeaponLength * 0.6, 0, 1.5 );
  1399. // Update state when a hit is registered
  1400. if (hit_fraction != 0)
  1401. {
  1402. vector v1 = hit_pos - weaponEnd;
  1403. vector v2 = hit_pos - end;
  1404. float d = vector.Dot(v1, v2);
  1405. // But leave some threshold where previous state is kept
  1406. // to prevent excessive switches from occuring
  1407. if (!wasLift && d > inThreshold)
  1408. {
  1409. wantsLift = true;
  1410. }
  1411. else if (wasLift && d < -outThreshold)
  1412. {
  1413. wantsLift = false;
  1414. }
  1415. m_LastLiftPosition = hit_pos;
  1416. m_LastLiftHit = player.GetSimulationTimeStamp();
  1417. }
  1418. else
  1419. {
  1420. // With no hit and no previous lift
  1421. if (lastLiftPosition == vector.Zero)
  1422. {
  1423. wantsLift = false;
  1424. m_LastLiftPosition = vector.Zero;
  1425. }
  1426. // See if previous hit wasn't very close to our current position,
  1427. // in which case simply don't lift the weapon
  1428. else
  1429. {
  1430. vector v3 = (lastLiftPosition - start).Normalized();
  1431. vector v4 = (end-start).Normalized();
  1432. float d2 = vector.Dot(v3, v4);
  1433. // no isect, angle delta check
  1434. if (Math.Acos(d2) > (angleThreshold * Math.DEG2RAD)) // if relative angle is > x degree, stop lifting
  1435. {
  1436. wantsLift = false;
  1437. m_LastLiftPosition = vector.Zero;
  1438. }
  1439. // no isect, distance check
  1440. else
  1441. {
  1442. float d3 = vector.Dot( lastLiftPosition - weaponEnd, (start-end).Normalized() );
  1443. if (d3 < -noIsctOutThreshold)
  1444. {
  1445. wantsLift = false;
  1446. m_LastLiftPosition = vector.Zero;
  1447. }
  1448. // fallback in case offending object disappears or moves
  1449. int timeSinceHit = player.GetSimulationTimeStamp() - m_LastLiftHit;
  1450. if (timeSinceHit > maxNumMissedTicks)
  1451. {
  1452. wantsLift = false;
  1453. m_LastLiftPosition = vector.Zero;
  1454. }
  1455. }
  1456. }
  1457. }
  1458. // lift is desired
  1459. if (wantsLift)
  1460. {
  1461. //Print(distance);
  1462. m_LiftWeapon = true;
  1463. return true;
  1464. }
  1465. return false;
  1466. }
  1467. //! Return whether provided material triggers weapon lift (true) or not (false).
  1468. bool LiftWeaponRaycastResultCheck(notnull RaycastRVResult res)
  1469. {
  1470. return res.surface.IsSolid();
  1471. }
  1472. //! Returns effective length of attachments that influence total weapon length
  1473. float GetEffectiveAttachmentLength()
  1474. {
  1475. ItemBase attachment;
  1476. if (HasBayonetAttached())
  1477. {
  1478. int bayonetIndex = GetBayonetAttachmentIdx();
  1479. attachment = ItemBase.Cast(GetInventory().FindAttachment(bayonetIndex));
  1480. }
  1481. else
  1482. {
  1483. attachment = GetAttachedSuppressor();
  1484. }
  1485. if (attachment)
  1486. {
  1487. return Math.Max(attachment.m_ItemModelLength + attachment.m_ItemAttachOffset, 0);
  1488. }
  1489. else
  1490. {
  1491. return 0;
  1492. }
  1493. }
  1494. void SetSyncJammingChance( float jamming_chance )
  1495. {
  1496. m_ChanceToJamSync = jamming_chance;
  1497. }
  1498. /**@fn EjectCartridge
  1499. * @brief unload bullet from chamber or internal magazine
  1500. *
  1501. * @NOTE: EjectCartridge = GetCartridgeInfo + PopCartridge
  1502. *
  1503. * @param[in] muzzleIndex
  1504. * @param[out] ammoDamage \p damage of the ammo
  1505. * @param[out] ammoTypeName \p type name of the ejected ammo
  1506. * @return true if bullet removed from chamber
  1507. **/
  1508. bool EjectCartridge(int muzzleIndex, out float ammoDamage, out string ammoTypeName)
  1509. {
  1510. if (IsChamberEjectable(muzzleIndex))
  1511. {
  1512. if (PopCartridgeFromChamber(muzzleIndex, ammoDamage, ammoTypeName))
  1513. return true;
  1514. }
  1515. else if (GetInternalMagazineCartridgeCount(muzzleIndex) > 0)
  1516. {
  1517. if (PopCartridgeFromInternalMagazine(muzzleIndex, ammoDamage, ammoTypeName))
  1518. return true;
  1519. }
  1520. return false;
  1521. }
  1522. bool CopyWeaponStateFrom(notnull Weapon_Base src)
  1523. {
  1524. float damage = 0.0;
  1525. string type;
  1526. for (int mi = 0; mi < src.GetMuzzleCount(); ++mi)
  1527. {
  1528. if (!src.IsChamberEmpty(mi))
  1529. {
  1530. if (src.GetCartridgeInfo(mi, damage, type))
  1531. {
  1532. PushCartridgeToChamber(mi, damage, type);
  1533. }
  1534. }
  1535. for (int ci = 0; ci < src.GetInternalMagazineCartridgeCount(mi); ++ci)
  1536. {
  1537. if (src.GetInternalMagazineCartridgeInfo(mi, ci, damage, type))
  1538. {
  1539. PushCartridgeToInternalMagazine(mi, damage, type);
  1540. }
  1541. }
  1542. }
  1543. int dummy_version = int.MAX;
  1544. PlayerBase parentPlayer = PlayerBase.Cast(src.GetHierarchyRootPlayer());
  1545. if (!parentPlayer)
  1546. dummy_version -= 1;
  1547. ScriptReadWriteContext ctx = new ScriptReadWriteContext;
  1548. src.OnStoreSave(ctx.GetWriteContext());
  1549. OnStoreLoad(ctx.GetReadContext(), dummy_version);
  1550. return true;
  1551. }
  1552. //! attachment helpers (firearm melee)
  1553. override void SetBayonetAttached(bool pState, int slot_idx = -1)
  1554. {
  1555. m_BayonetAttached = pState;
  1556. m_BayonetAttachmentIdx = slot_idx;
  1557. }
  1558. override bool HasBayonetAttached()
  1559. {
  1560. return m_BayonetAttached;
  1561. }
  1562. override int GetBayonetAttachmentIdx()
  1563. {
  1564. return m_BayonetAttachmentIdx;
  1565. }
  1566. override void SetButtstockAttached(bool pState, int slot_idx = -1)
  1567. {
  1568. m_ButtstockAttached = pState;
  1569. m_ButtstockAttachmentIdx = slot_idx;
  1570. }
  1571. override bool HasButtstockAttached()
  1572. {
  1573. return m_ButtstockAttached;
  1574. }
  1575. override int GetButtstockAttachmentIdx()
  1576. {
  1577. return m_ButtstockAttachmentIdx;
  1578. }
  1579. void HideWeaponBarrel(bool state)
  1580. {
  1581. if ( !GetGame().IsDedicatedServer() )//hidden for client only
  1582. {
  1583. ItemOptics optics = GetAttachedOptics();
  1584. if ( optics && !optics.AllowsDOF() && m_weaponHideBarrelIdx != -1 )
  1585. {
  1586. SetSimpleHiddenSelectionState(m_weaponHideBarrelIdx,!state);
  1587. }
  1588. }
  1589. }
  1590. void ShowMagazine()
  1591. {
  1592. if (m_magazineSimpleSelectionIndex > -1)
  1593. SetSimpleHiddenSelectionState(m_magazineSimpleSelectionIndex,1);
  1594. else
  1595. SelectionMagazineShow();
  1596. }
  1597. void HideMagazine()
  1598. {
  1599. if (m_magazineSimpleSelectionIndex > -1)
  1600. SetSimpleHiddenSelectionState(m_magazineSimpleSelectionIndex,0);
  1601. else
  1602. SelectionMagazineHide();
  1603. }
  1604. override EntityAI ProcessMeleeItemDamage(int mode = 0)
  1605. {
  1606. EntityAI attachment;
  1607. switch (mode)
  1608. {
  1609. case 0:
  1610. super.ProcessMeleeItemDamage();
  1611. break;
  1612. case 1:
  1613. attachment = GetInventory().FindAttachment(m_ButtstockAttachmentIdx);
  1614. break;
  1615. case 2:
  1616. attachment = GetInventory().FindAttachment(m_BayonetAttachmentIdx);
  1617. break;
  1618. default:
  1619. super.ProcessMeleeItemDamage();
  1620. break;
  1621. }
  1622. if (attachment)
  1623. {
  1624. attachment.ProcessMeleeItemDamage();
  1625. return attachment;
  1626. }
  1627. return this;
  1628. }
  1629. bool IsShowingChamberedBullet()
  1630. {
  1631. return true;
  1632. }
  1633. int GetBurstCount()
  1634. {
  1635. return m_BurstCount;
  1636. }
  1637. void ResetBurstCount()
  1638. {
  1639. m_BurstCount = 0;
  1640. }
  1641. override void SetActions()
  1642. {
  1643. super.SetActions();
  1644. AddAction(FirearmActionUnjam);
  1645. AddAction(FirearmActionAttachMagazine);
  1646. AddAction(FirearmActionLoadBullet);
  1647. AddAction(FirearmActionMechanicManipulate);
  1648. AddAction(ActionTurnOnWeaponFlashlight);
  1649. AddAction(ActionTurnOffWeaponFlashlight);
  1650. AddAction(FirearmActionAttachMagazineQuick); // Easy reload
  1651. AddAction(FirearmActionLoadBulletQuick); // Easy reload
  1652. }
  1653. override bool CanBeUsedForSuicide()
  1654. {
  1655. if (!ConfigGetBool("isSuicideWeapon"))
  1656. return false;
  1657. return super.CanBeUsedForSuicide();
  1658. }
  1659. //Debug menu Spawn Ground Special
  1660. override void OnDebugSpawn()
  1661. {
  1662. SpawnAmmo("", SAMF_DEFAULT);
  1663. }
  1664. bool AddJunctureToAttachedMagazine(PlayerBase player, int timeoutMS)
  1665. {
  1666. Magazine mag = GetMagazine(GetCurrentMuzzle());
  1667. InventoryLocation il = new InventoryLocation();
  1668. if (mag)
  1669. {
  1670. return GetGame().AddInventoryJunctureEx(player, mag, il, false, timeoutMS);
  1671. }
  1672. return true;
  1673. }
  1674. void ClearJunctureToAttachedMagazine(PlayerBase player)
  1675. {
  1676. Magazine mag = GetMagazine(GetCurrentMuzzle());
  1677. if (mag)
  1678. {
  1679. GetGame().ClearJuncture(player, mag);
  1680. }
  1681. }
  1682. void SetNextWeaponMode(int muzzleIndex)
  1683. {
  1684. SetNextMuzzleMode(muzzleIndex);
  1685. }
  1686. // CoolDown
  1687. void SetCoolDown( float coolDownTime )
  1688. {
  1689. m_coolDownTime = coolDownTime;
  1690. }
  1691. void UpdateCoolDown( float dt ) { m_coolDownTime -= dt; }
  1692. bool IsCoolDown()
  1693. {
  1694. return m_coolDownTime > 0;
  1695. }
  1696. };