weapon_base.c 52 KB

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