weapon_base.c 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288
  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 float m_ObstructionDistance;
  64. protected vector m_LastLiftPosition;
  65. protected int m_LastLiftHit;
  66. ref array<int> m_bulletSelectionIndex = new array<int>;
  67. ref array<float> m_DOFProperties;
  68. ref array<float> m_ChanceToJam = new array<float>;
  69. protected float m_ChanceToJamSync = 0;
  70. protected ref PropertyModifiers m_PropertyModifierObject;
  71. 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;
  72. protected ref Timer m_DelayedValidationTimer;
  73. private float m_coolDownTime = 0;
  74. void Weapon_Base()
  75. {
  76. //m_DmgPerShot = ConfigGetFloat("damagePerShot");
  77. m_BayonetAttached = false;
  78. m_ButtstockAttached = false;
  79. m_WasIronSight = true; // initially uses ironsights by default
  80. m_BayonetAttachmentIdx = -1;
  81. m_ButtstockAttachmentIdx = -1;
  82. m_BurstCount = 0;
  83. m_DOFProperties = new array<float>;
  84. if (GetGame().IsClient())
  85. {
  86. m_DelayedValidationTimer = new Timer();
  87. }
  88. if ( ConfigIsExisting("simpleHiddenSelections") )
  89. {
  90. TStringArray selectionNames = new TStringArray;
  91. ConfigGetTextArray("simpleHiddenSelections",selectionNames);
  92. m_weaponHideBarrelIdx = selectionNames.Find("hide_barrel");
  93. m_magazineSimpleSelectionIndex = selectionNames.Find("magazine");
  94. int bulletIndex = selectionNames.Find("bullet");
  95. if ( bulletIndex != -1 )
  96. {
  97. m_bulletSelectionIndex.Insert(bulletIndex);
  98. for (int i = 2; i < 100; i++)
  99. {
  100. bulletIndex = selectionNames.Find(string.Format("bullet%1",i));
  101. if (bulletIndex != -1)
  102. {
  103. m_bulletSelectionIndex.Insert(bulletIndex);
  104. }
  105. else
  106. {
  107. break;
  108. }
  109. }
  110. }
  111. }
  112. InitWeaponLength();
  113. InitWeaponLiftCheckVerticalOffset();
  114. InitShoulderDistance();
  115. InitObstructionDistance();
  116. InitDOFProperties(m_DOFProperties);
  117. if (GetGame().IsServer())
  118. {
  119. InitReliability(m_ChanceToJam);
  120. }
  121. InitStateMachine();
  122. }
  123. void InitStateMachine() { }
  124. override void EEInit()
  125. {
  126. super.EEInit();
  127. if (GetGame().IsServer())
  128. {
  129. GetGame().GetCallQueue( CALL_CATEGORY_GAMEPLAY ).Call( AssembleGun );
  130. }
  131. }
  132. void SetInitialState(WeaponStableState initState)
  133. {
  134. m_fsm.SetInitialState(initState);
  135. SetCharged(!initState.IsDischarged());
  136. SetWeaponOpen(initState.IsWeaponOpen());
  137. SetGroundAnimFrameIndex(initState.m_animState);
  138. }
  139. bool IsCharged()
  140. {
  141. return m_Charged;
  142. }
  143. void SetCharged(bool value)
  144. {
  145. m_Charged = value;
  146. }
  147. bool IsWeaponOpen()
  148. {
  149. return m_WeaponOpen;
  150. }
  151. void SetWeaponOpen(bool value)
  152. {
  153. m_WeaponOpen = value;
  154. }
  155. override protected float GetWeightSpecialized(bool forceRecalc = false)
  156. {
  157. float baseWeight = GetInventoryAndCargoWeight(forceRecalc);
  158. float ammoWeight;
  159. float ammoDamage;
  160. string bulletTypeName, ammoTypeName;
  161. int muzzleCount = GetMuzzleCount();
  162. #ifdef DEVELOPER
  163. if (WeightDebug.m_VerbosityFlags & WeightDebugType.RECALC_FORCED)
  164. {
  165. WeightDebugData data1 = WeightDebug.GetWeightDebug(this);
  166. data1.SetCalcDetails("TWPN: " + m_ConfigWeight+"(item weight) + " + baseWeight +"(contents weight)" );
  167. }
  168. #endif
  169. for (int muzzleIndex = 0; muzzleIndex < muzzleCount; muzzleIndex++)
  170. {
  171. //chamber weight
  172. if (!IsChamberEmpty(muzzleIndex))
  173. {
  174. ammoTypeName = GetChamberAmmoTypeName(muzzleIndex);
  175. ammoWeight += g_Game.ConfigGetFloat(string.Format("CfgMagazines %1 weight", ammoTypeName));
  176. #ifdef DEVELOPER
  177. if (WeightDebug.m_VerbosityFlags & WeightDebugType.RECALC_FORCED)
  178. {
  179. WeightDebugData data2 = WeightDebug.GetWeightDebug(this);
  180. data2.AddCalcDetails( g_Game.ConfigGetFloat("CfgMagazines " + ammoTypeName + " weight").ToString() +"(chamber weight)");
  181. }
  182. #endif
  183. }
  184. //correctly calculates internal magazine weight based on the ammo type of each bullet
  185. if (HasInternalMagazine(muzzleIndex))
  186. {
  187. #ifdef DEVELOPER
  188. float debugInternalMagWeight;
  189. #endif
  190. int cartridgeCount = GetInternalMagazineCartridgeCount(muzzleIndex);
  191. for (int cartridgeIndex = 0; cartridgeIndex < cartridgeCount; cartridgeIndex++)
  192. {
  193. GetInternalMagazineCartridgeInfo(muzzleIndex, cartridgeIndex, ammoDamage, bulletTypeName);
  194. ammoWeight += Ammunition_Base.GetAmmoWeightByBulletType(bulletTypeName);
  195. #ifdef DEVELOPER
  196. debugInternalMagWeight += g_Game.ConfigGetFloat("CfgMagazines " + ammoTypeName + " weight");
  197. #endif
  198. }
  199. #ifdef DEVELOPER
  200. if (WeightDebug.m_VerbosityFlags & WeightDebugType.RECALC_FORCED)
  201. {
  202. WeightDebugData data3 = WeightDebug.GetWeightDebug(this);
  203. data3.AddCalcDetails(debugInternalMagWeight.ToString()+ "(internal mag weight)");
  204. }
  205. #endif
  206. }
  207. }
  208. return ammoWeight + baseWeight + GetConfigWeightModified();
  209. }
  210. //! override on weapons with some assembly required
  211. void AssembleGun();
  212. bool CanProcessAction(int action, int actionType)
  213. {
  214. return false; // @TODO
  215. }
  216. /**@fn HasActionAbility
  217. * @brief query if weapon supports action and actionType
  218. * @param[in] action \p one of Human.actions (i.e. RELOAD, MECHANISM, ...)
  219. * @param[in] actionType \p one of Human.actionTypes (i.e. CHAMBERING_ONEBULLET_CLOSED, MECHANISM_CLOSED...)
  220. * @return true if weapon supports operation
  221. **/
  222. bool HasActionAbility(int action, int actionType)
  223. {
  224. int count = GetAbilityCount();
  225. for (int i = 0; i < count; ++i)
  226. {
  227. AbilityRecord rec = GetAbility(i);
  228. if (rec.m_action == action && rec.m_actionType == actionType)
  229. return true;
  230. }
  231. return false;
  232. }
  233. /**@fn GetAbilityCount
  234. * @return number of stored abilities
  235. **/
  236. int GetAbilityCount() { return m_abilities.Count(); }
  237. /**@fn GetAbility
  238. * @param[in] index \p index into m_abilities storage
  239. * @return ability record
  240. **/
  241. AbilityRecord GetAbility(int index) { return m_abilities.Get(index); }
  242. /**@fn CanProcessWeaponEvents
  243. * @return true if weapon has running fsm
  244. **/
  245. bool CanProcessWeaponEvents() { return m_fsm && m_fsm.IsRunning(); }
  246. /**@fn GetCurrentState
  247. * @brief returns currently active state
  248. * @return current state the FSM is in (or NULL)
  249. **/
  250. WeaponStateBase GetCurrentState() { return m_fsm.GetCurrentState(); }
  251. /**@fn IsWaitingForActionFinish
  252. * @brief returns true if state machine started playing action/actionType and waits for finish
  253. **/
  254. bool IsWaitingForActionFinish()
  255. {
  256. return CanProcessWeaponEvents() && GetCurrentState().IsWaitingForActionFinish();
  257. }
  258. bool IsIdle()
  259. {
  260. return CanProcessWeaponEvents() && GetCurrentState().IsIdle();
  261. }
  262. /**@fn ProcessWeaponEvent
  263. * @brief weapon's fsm handling of events
  264. * @NOTE: warning: ProcessWeaponEvent can be called only within DayZPlayer::HandleWeapons (or ::CommandHandler)
  265. **/
  266. bool ProcessWeaponEvent(WeaponEventBase e)
  267. {
  268. SyncEventToRemote(e);
  269. // @NOTE: synchronous events not handled by fsm
  270. if (e.GetEventID() == WeaponEventID.SET_NEXT_MUZZLE_MODE)
  271. {
  272. SetNextWeaponMode(GetCurrentMuzzle());
  273. return true;
  274. }
  275. if (m_fsm.ProcessEvent(e) == ProcessEventResult.FSM_OK)
  276. return true;
  277. //if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("FSM refused to process event (no transition): src=" + GetCurrentState().ToString() + " event=" + e.ToString()); }
  278. return false;
  279. }
  280. /**@fn ProcessWeaponAbortEvent
  281. * @NOTE: warning: ProcessWeaponEvent can be called only within DayZPlayer::HandleWeapons (or ::CommandHandler)
  282. **/
  283. bool ProcessWeaponAbortEvent(WeaponEventBase e)
  284. {
  285. SyncEventToRemote(e);
  286. ProcessEventResult aa;
  287. m_fsm.ProcessAbortEvent(e, aa);
  288. return aa == ProcessEventResult.FSM_OK;
  289. }
  290. bool CanChamberBullet(int muzzleIndex, Magazine mag)
  291. {
  292. return CanChamberFromMag(muzzleIndex, mag) && (!IsChamberFull(muzzleIndex) || IsChamberFiredOut(muzzleIndex) || !IsInternalMagazineFull(muzzleIndex));
  293. }
  294. void SetWeaponAnimState(int state)
  295. {
  296. m_weaponAnimState = state;
  297. SetGroundAnimFrameIndex(state);
  298. }
  299. void ResetWeaponAnimState()
  300. {
  301. if (LogManager.IsWeaponLogEnable()) fsmDebugSpam("[wpnfsm] " + Object.GetDebugName(this) + " resetting anim state: " + typename.EnumToString(PistolAnimState, m_weaponAnimState) + " --> " + typename.EnumToString(PistolAnimState, -1));
  302. m_weaponAnimState = -1;
  303. }
  304. int GetWeaponAnimState() { return m_weaponAnimState; }
  305. void EEFired(int muzzleType, int mode, string ammoType)
  306. {
  307. if ( !GetGame().IsDedicatedServer() )
  308. {
  309. ItemBase suppressor = GetAttachedSuppressor();
  310. // Muzzle flash & overheating effects
  311. ItemBase.PlayFireParticles(this, muzzleType, ammoType, this, suppressor, "CfgWeapons" );
  312. IncreaseOverheating(this, ammoType, this, suppressor, "CfgWeapons");
  313. if (suppressor)
  314. {
  315. ItemBase.PlayFireParticles(this, muzzleType, ammoType, suppressor, NULL, "CfgVehicles" );
  316. suppressor.IncreaseOverheating(this, ammoType, this, suppressor, "CfgVehicles");
  317. }
  318. }
  319. //obsolete, replaced by C++ solution!
  320. /*
  321. if (GetGame().IsServer())
  322. {
  323. AddHealth("","Health",-m_DmgPerShot); //damages weapon
  324. if (suppressor)
  325. suppressor.AddHealth("","Health",-m_DmgPerShot); //damages suppressor; TODO add suppressor damage coeficient/parameter (?) to suppressors/weapons (?)
  326. }
  327. */
  328. //JamCheck(muzzleType);
  329. #ifdef DIAG_DEVELOPER
  330. MiscGameplayFunctions.UnlimitedAmmoDebugCheck(this);
  331. #endif
  332. }
  333. bool JamCheck(int muzzleIndex )
  334. {
  335. PlayerBase player = PlayerBase.Cast(GetHierarchyRootPlayer());
  336. if ( player )
  337. {
  338. float rnd = player.GetRandomGeneratorSyncManager().GetRandom01(RandomGeneratorSyncUsage.RGSJam);
  339. //Print("Random Jam - " + rnd);
  340. if (rnd < GetSyncChanceToJam())
  341. return true;
  342. }
  343. return false;
  344. }
  345. void ShowBullet(int muzzleIndex)
  346. {
  347. if ( m_bulletSelectionIndex.Count() > muzzleIndex )
  348. {
  349. SetSimpleHiddenSelectionState(m_bulletSelectionIndex[muzzleIndex],1);
  350. }
  351. else
  352. SelectionBulletShow();
  353. }
  354. void HideBullet(int muzzleIndex)
  355. {
  356. if ( m_bulletSelectionIndex.Count() > muzzleIndex )
  357. {
  358. SetSimpleHiddenSelectionState(m_bulletSelectionIndex[muzzleIndex],0);
  359. }
  360. else
  361. SelectionBulletHide();
  362. }
  363. bool IsJammed() { return m_isJammed; }
  364. bool CanEjectBullet() {return true;}
  365. void SetJammed(bool value) { m_isJammed = value; }
  366. float GetSyncChanceToJam() { return m_ChanceToJamSync; }
  367. float GetChanceToJam()
  368. {
  369. int level = GetHealthLevel();
  370. if (level >= 0 && level < m_ChanceToJam.Count())
  371. return m_ChanceToJam[level];
  372. else
  373. return 0.0;
  374. }
  375. void SyncSelectionState(bool has_bullet, bool has_mag)
  376. {
  377. if (has_bullet)
  378. {
  379. string chamberedAmmoTypeName;
  380. float chamberedAmmoDmg;
  381. if ( GetCartridgeInfo(0, chamberedAmmoDmg, chamberedAmmoTypeName) )
  382. {
  383. EffectBulletShow(0, chamberedAmmoDmg, chamberedAmmoTypeName);
  384. }
  385. //ShowBullet(0);
  386. SelectionBulletShow();
  387. }
  388. else
  389. {
  390. //HideBullet(0);
  391. SelectionBulletHide();
  392. EffectBulletHide(0);
  393. }
  394. if (has_mag)
  395. ShowMagazine();
  396. else
  397. HideMagazine();
  398. }
  399. /*override void EEOnAfterLoad()
  400. {
  401. super.EEOnAfterLoad();
  402. string chamberedAmmoTypeName;
  403. float chamberedAmmoDmg;
  404. if ( GetCartridgeInfo(0, chamberedAmmoDmg, chamberedAmmoTypeName) )
  405. {
  406. EffectBulletShow(0, chamberedAmmoDmg, chamberedAmmoTypeName);
  407. }
  408. }*/
  409. void ForceSyncSelectionState()
  410. {
  411. int nMuzzles = GetMuzzleCount();
  412. for (int i = 0; i < nMuzzles; ++i)
  413. {
  414. if (IsChamberFull(i))
  415. {
  416. ShowBullet(i);
  417. float damage;
  418. string ammoTypeName;
  419. GetCartridgeInfo(i, damage, ammoTypeName);
  420. EffectBulletShow(i, damage, ammoTypeName);
  421. }
  422. else
  423. {
  424. HideBullet(i);
  425. EffectBulletHide(i);
  426. }
  427. Magazine mag = GetMagazine(i);
  428. if (mag)
  429. ShowMagazine();
  430. else
  431. HideMagazine();
  432. }
  433. }
  434. override bool OnStoreLoad(ParamsReadContext ctx, int version)
  435. {
  436. if ( !super.OnStoreLoad(ctx, version) )
  437. return false;
  438. if (version >= 113)
  439. {
  440. int current_muzzle = 0;
  441. if (!ctx.Read(current_muzzle))
  442. {
  443. Error("Weapon.OnStoreLoad " + this + " cannot read current muzzle!");
  444. return false;
  445. }
  446. if (current_muzzle >= GetMuzzleCount() || current_muzzle < 0)
  447. Error("Weapon.OnStoreLoad " + this + " trying to set muzzle index " + current_muzzle + " while it only has " + GetMuzzleCount() + " muzzles!");
  448. else
  449. SetCurrentMuzzle(current_muzzle);
  450. }
  451. if (version >= 105)
  452. {
  453. int mode_count = 0;
  454. if (!ctx.Read(mode_count))
  455. {
  456. Error("Weapon.OnStoreLoad " + this + " cannot read mode count!");
  457. return false;
  458. }
  459. for (int m = 0; m < mode_count; ++m)
  460. {
  461. int mode = 0;
  462. if (!ctx.Read(mode))
  463. {
  464. Error("Weapon.OnStoreLoad " + this + " cannot read mode[" + m + "]");
  465. return false;
  466. }
  467. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " OnStoreLoad - loaded muzzle[" + m + "].mode = " + mode); }
  468. SetCurrentMode(m, mode);
  469. }
  470. }
  471. if ( version >= 106 )
  472. {
  473. if ( !ctx.Read(m_isJammed) )
  474. {
  475. Error("Weapon.OnStoreLoad cannot load jamming state");
  476. return false;
  477. }
  478. }
  479. if (m_fsm)
  480. {
  481. if (!m_fsm.OnStoreLoad(ctx, version))
  482. return false;
  483. WeaponStableState wss = WeaponStableState.Cast(m_fsm.GetCurrentState());
  484. if (wss)
  485. {
  486. SetGroundAnimFrameIndex(wss.m_animState);
  487. }
  488. }
  489. else
  490. {
  491. int dummy = 0;
  492. if (!ctx.Read(dummy))
  493. return false;
  494. }
  495. return true;
  496. }
  497. void SaveCurrentFSMState(ParamsWriteContext ctx)
  498. {
  499. if (m_fsm && m_fsm.IsRunning())
  500. {
  501. if (m_fsm.SaveCurrentFSMState(ctx))
  502. {
  503. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " state saved."); }
  504. }
  505. else
  506. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " state NOT saved.");
  507. }
  508. else
  509. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon.SaveCurrentFSMState: trying to save weapon without FSM (or uninitialized weapon) this=" + this + " type=" + GetType());
  510. }
  511. bool LoadCurrentFSMState(ParamsReadContext ctx, int version)
  512. {
  513. if (m_fsm)
  514. {
  515. if (m_fsm.LoadCurrentFSMState(ctx, version))
  516. {
  517. WeaponStableState state = WeaponStableState.Cast(GetCurrentState());
  518. if (state)
  519. {
  520. SyncSelectionState(state.HasBullet(), state.HasMagazine());
  521. state.SyncAnimState();
  522. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " stable state loaded and synced."); }
  523. return true;
  524. }
  525. else
  526. {
  527. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " unstable/error state loaded."); }
  528. return false;
  529. }
  530. }
  531. else
  532. {
  533. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " did not load.");
  534. return false;
  535. }
  536. }
  537. else
  538. {
  539. Error("[wpnfsm] " + Object.GetDebugName(this) + " Weapon.LoadCurrentFSMState: trying to load weapon without FSM (or uninitialized weapon) this=" + this + " type=" + GetType());
  540. return false;
  541. }
  542. }
  543. override void AfterStoreLoad()
  544. {
  545. if (m_fsm)
  546. {
  547. int mi = GetCurrentMuzzle();
  548. Magazine mag = GetMagazine(mi);
  549. bool has_mag = mag != null;
  550. bool has_bullet = !IsChamberEmpty(mi);
  551. SyncSelectionState(has_bullet, has_mag);
  552. }
  553. }
  554. override void OnStoreSave(ParamsWriteContext ctx)
  555. {
  556. super.OnStoreSave(ctx);
  557. // current muzzle added in version 113
  558. int current_muzzle = GetCurrentMuzzle();
  559. ctx.Write(current_muzzle);
  560. // fire mode added in version 105
  561. int mode_count = GetMuzzleCount();
  562. ctx.Write(mode_count);
  563. for (int m = 0; m < mode_count; ++m)
  564. ctx.Write(GetCurrentMode(m));
  565. ctx.Write(m_isJammed);
  566. if (m_fsm)
  567. m_fsm.OnStoreSave(ctx);
  568. else
  569. {
  570. int dummy = 0;
  571. ctx.Write(dummy);
  572. }
  573. }
  574. /**@fn GetCurrentStateID
  575. * @brief tries to return identifier of current state
  576. **/
  577. int GetInternalStateID()
  578. {
  579. if (m_fsm)
  580. return m_fsm.GetInternalStateID();
  581. return 0;
  582. }
  583. /**@fn GetCurrentStableStateID
  584. * @brief tries to return identifier of current stable state (or nearest stable state if unstable state is currently running)
  585. **/
  586. int GetCurrentStableStateID()
  587. {
  588. if (m_fsm)
  589. {
  590. return m_fsm.GetCurrentStableStateID();
  591. }
  592. return 0;
  593. }
  594. /**@fn RandomizeFSMState
  595. * @brief With the parameters given, selects a random suitable state for the FSM of the weapon
  596. * @WARNING: Weapon_Base.Synchronize call might be needed, if this method is called while clients are connected
  597. **/
  598. void RandomizeFSMState()
  599. {
  600. if (m_fsm)
  601. {
  602. int mi = GetCurrentMuzzle();
  603. Magazine mag = GetMagazine(mi);
  604. bool has_mag = mag != null;
  605. bool has_bullet = !IsChamberEmpty(mi);
  606. bool has_jam = IsJammed();
  607. array<MuzzleState> muzzleStates = GetMuzzleStates();
  608. m_fsm.RandomizeFSMStateEx(muzzleStates, has_mag, has_jam);
  609. ForceSyncSelectionState();
  610. }
  611. }
  612. //! Helper method for RandomizeFSMState
  613. protected array<MuzzleState> GetMuzzleStates()
  614. {
  615. array<MuzzleState> muzzleStates = new array<MuzzleState>;
  616. int nMuzzles = GetMuzzleCount();
  617. for (int i = 0; i < nMuzzles; ++i)
  618. {
  619. MuzzleState state = MuzzleState.U;
  620. if (IsChamberFiredOut(i))
  621. state = MuzzleState.F;
  622. else if (IsChamberFull(i))
  623. state = MuzzleState.L;
  624. else if (IsChamberEmpty(i))
  625. state = MuzzleState.E;
  626. else
  627. ErrorEx(string.Format("Unable to identify chamber state of muzzle %1", i));
  628. muzzleStates.Insert(state);
  629. }
  630. return muzzleStates;
  631. }
  632. /** \name Weapon With Ammo
  633. * Helpers for spawning ammo/magazine in weapon
  634. * For the flags, either a combination of WeaponWithAmmoFlags can be used
  635. * Or one of the preset 'const int' with 'SAMF_' prefix (SAMF_DEFAULT, SAMF_RNG)
  636. */
  637. //@{
  638. /**@fn CreateWeaponWithAmmo
  639. * @brief Create weapon with ammo
  640. * @param[in] weaponType \p string The weapon to create
  641. * @param[in] magazineType \p string The magazine to attach or ammo to load, passing in empty string will select random
  642. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  643. * @return The created weapon
  644. **/
  645. static Weapon_Base CreateWeaponWithAmmo( string weaponType, string magazineType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  646. {
  647. Weapon_Base wpn = Weapon_Base.Cast(GetGame().CreateObjectEx( weaponType, vector.Zero, ECE_PLACE_ON_SURFACE ));
  648. if ( !wpn )
  649. {
  650. ErrorEx(string.Format("%1 does not exist or is not a weapon.", weaponType));
  651. return null;
  652. }
  653. wpn.SpawnAmmo(magazineType, flags);
  654. return wpn;
  655. }
  656. /**@fn SpawnAmmo
  657. * @brief General method trying to attch magazine, fill inner magazine and fill chamber
  658. * @param[in] magazineType \p string The magazine to attach or ammo to load, passing in empty string will select random
  659. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  660. * @return whether anything was spawned or done
  661. **/
  662. bool SpawnAmmo( string magazineType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  663. {
  664. // Attempt internal mag
  665. if ( HasInternalMagazine(-1) && FillInnerMagazine(magazineType, flags) )
  666. return true;
  667. // Attempt mag attachment
  668. if ( GetMagazineTypeCount(0) > 0 && SpawnAttachedMagazine(magazineType, flags) )
  669. return true;
  670. // Attempt chamber
  671. if ( FillChamber(magazineType, flags) )
  672. return true;
  673. return false;
  674. }
  675. /**@fn SpawnAttachedMagazine
  676. * @brief Try to spawn and attach a magazine
  677. * @param[in] magazineType \p string The magazine to attach, passing in empty string will select random
  678. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  679. * @return The created magazine or null
  680. **/
  681. Magazine SpawnAttachedMagazine( string magazineType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  682. {
  683. // Check if the gun has any magazines registered in config
  684. if ( GetMagazineTypeCount(0) == 0 )
  685. {
  686. ErrorEx(string.Format("No 'magazines' config entry for %1.", this));
  687. return null;
  688. }
  689. // Randomize when no specific one is given
  690. if ( magazineType == "" )
  691. {
  692. if ( flags & WeaponWithAmmoFlags.MAX_CAPACITY_MAG)
  693. magazineType = GetMaxMagazineTypeName(0);
  694. else
  695. magazineType = GetRandomMagazineTypeName(0);
  696. }
  697. EntityAI magAI = GetInventory().CreateAttachment(magazineType);
  698. if (!magAI)
  699. {
  700. ErrorEx(string.Format("Failed to create and attach %1 to %2", GetDebugName(magAI), this));
  701. return null;
  702. }
  703. Magazine mag;
  704. if (!CastTo(mag, magAI))
  705. {
  706. ErrorEx(string.Format("Expected magazine, created: %1", GetDebugName(magAI)));
  707. return null;
  708. }
  709. // Decide random quantity when enabled
  710. if (flags & WeaponWithAmmoFlags.QUANTITY_RNG)
  711. mag.ServerSetAmmoCount(Math.RandomIntInclusive(0, mag.GetAmmoMax()));
  712. if(MustBeChambered(0))
  713. {
  714. string bulletType;
  715. float dmg;
  716. if(mag.ServerAcquireCartridge(dmg,bulletType))
  717. {
  718. FillChamber(bulletType, flags);
  719. }
  720. }
  721. // Fill chamber when flagged
  722. bool chamberRng = (flags & WeaponWithAmmoFlags.CHAMBER_RNG);
  723. bool chamber = (flags & WeaponWithAmmoFlags.CHAMBER) || chamberRng;
  724. if (chamber || chamberRng)
  725. {
  726. FillChamber(magazineType, flags);
  727. }
  728. // FSM cares about magazine state
  729. RandomizeFSMState();
  730. Synchronize();
  731. return mag;
  732. }
  733. /**@fn FillInnerMagazine
  734. * @brief Try to fill the inner magazine
  735. * @param[in] ammoType \p string The ammo to load, passing in empty string will select random
  736. * @note It is best to fill in the actual 'ammo', as in the ones usually prefixed by 'Bullet_', to skip searching for it
  737. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  738. * @return Whether any ammo was added to the gun or not
  739. **/
  740. bool FillInnerMagazine( string ammoType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  741. {
  742. // Don't try to fill it when there are none
  743. if (!HasInternalMagazine(-1))
  744. return false;
  745. // Make sure the given ammoType is actually useable
  746. if (ammoType != "")
  747. {
  748. if (!AmmoTypesAPI.MagazineTypeToAmmoType(ammoType, ammoType))
  749. return false;
  750. }
  751. bool didSomething = false;
  752. bool needUpdateStateMachine = false;
  753. int muzzCount = GetMuzzleCount();
  754. bool ammoRng = ammoType == "";
  755. bool ammoFullRng = ammoRng && (flags & WeaponWithAmmoFlags.AMMO_MAG_RNG);
  756. // No full RNG flag, so pick one random and use only this one
  757. if (ammoRng && !ammoFullRng)
  758. ammoType = GetRandomChamberableAmmoTypeName(0);
  759. // Fill the internal magazine
  760. for (int i = 0; i < muzzCount; ++i)
  761. {
  762. bool loadAnyBullet = false;
  763. int ammoCount = GetInternalMagazineMaxCartridgeCount(i);
  764. // Decide random quantity when enabled
  765. if ( flags & WeaponWithAmmoFlags.QUANTITY_RNG )
  766. ammoCount = Math.RandomIntInclusive(0, ammoCount);
  767. // Only do the things when there is actually ammo to fill
  768. if (ammoCount > 0)
  769. {
  770. // Push in the cartridges
  771. for (int j = 0; j < ammoCount; ++j)
  772. {
  773. // Full random, decide a new one for every cartridge
  774. if ( ammoFullRng )
  775. ammoType = GetRandomChamberableAmmoTypeName(i);
  776. PushCartridgeToInternalMagazine(i, 0, ammoType);
  777. loadAnyBullet = true;
  778. didSomething = true;
  779. }
  780. if (loadAnyBullet && MustBeChambered(i))
  781. {
  782. if ( ammoFullRng )
  783. ammoType = GetRandomChamberableAmmoTypeName(i);
  784. if (FillSpecificChamber(i, 0, ammoType))
  785. needUpdateStateMachine = true;
  786. }
  787. }
  788. }
  789. // Call the chamber method if asked for
  790. bool chamber = (flags & WeaponWithAmmoFlags.CHAMBER) || (flags & WeaponWithAmmoFlags.CHAMBER_RNG);
  791. if (chamber && FillChamber(ammoType, flags))
  792. {
  793. didSomething = true;
  794. }
  795. // Only fix the FSM and Synchronize when absolutely needed
  796. if (!didSomething)
  797. return false;
  798. if( needUpdateStateMachine )
  799. {
  800. // FSM cares about chamber state
  801. RandomizeFSMState();
  802. Synchronize();
  803. }
  804. return true;
  805. }
  806. /**@fn FillChamber
  807. * @brief Try to fill the chamber
  808. * @param[in] ammoType \p string The ammo to load, passing in empty string will select random
  809. * @note It is best to fill in the actual 'ammo', as in the ones usually prefixed by 'Bullet_', to skip searching for it
  810. * @param[in] flags \p int Setup flags, please read WeaponWithAmmoFlags
  811. * @return Whether any chamber was filled
  812. **/
  813. bool FillChamber( string ammoType = "", int flags = WeaponWithAmmoFlags.CHAMBER )
  814. {
  815. bool didSomething = false;
  816. bool chamberFullRng = (flags & WeaponWithAmmoFlags.CHAMBER_RNG_SPORADIC);
  817. bool chamberRng = (flags & WeaponWithAmmoFlags.CHAMBER_RNG);
  818. bool chamber = (flags & WeaponWithAmmoFlags.CHAMBER);
  819. if (chamber || chamberFullRng)
  820. {
  821. // Make sure the given ammoType is actually useable
  822. if (ammoType != "")
  823. {
  824. if (!AmmoTypesAPI.MagazineTypeToAmmoType(ammoType, ammoType))
  825. return false;
  826. }
  827. else if (!(flags & WeaponWithAmmoFlags.AMMO_CHAMBER_RNG))
  828. {
  829. ammoType = GetRandomChamberableAmmoTypeName(0);
  830. }
  831. // fill chambers
  832. int muzzCount = GetMuzzleCount();
  833. int amountToChamber = muzzCount;
  834. if (chamberRng)
  835. amountToChamber = Math.RandomIntInclusive(0, muzzCount);
  836. for (int m = 0; m < muzzCount; ++m)
  837. {
  838. if (chamberFullRng)
  839. chamber = Math.RandomIntInclusive(0, 1);
  840. if (chamber)
  841. {
  842. if (FillSpecificChamber(m))
  843. {
  844. didSomething = true;
  845. amountToChamber--;
  846. if (amountToChamber <= 0)
  847. break;
  848. }
  849. }
  850. }
  851. }
  852. if (!didSomething)
  853. return false;
  854. // FSM cares about chamber state
  855. RandomizeFSMState();
  856. Synchronize();
  857. return true;
  858. }
  859. bool FillSpecificChamber(int muzzleIndex, float dmg = 0, string ammoType = "")
  860. {
  861. if(!IsChamberEmpty(muzzleIndex))
  862. return false;
  863. if (ammoType == "")
  864. {
  865. ammoType = GetRandomChamberableAmmoTypeName(muzzleIndex);
  866. }
  867. else
  868. {
  869. if (!AmmoTypesAPI.MagazineTypeToAmmoType(ammoType, ammoType))
  870. return false;
  871. }
  872. return PushCartridgeToChamber(muzzleIndex, dmg, ammoType);
  873. }
  874. //@}
  875. /**
  876. * @brief Returns number of slots for attachments corrected for weapons
  877. **/
  878. override int GetSlotsCountCorrect()
  879. {
  880. int ac = GetInventory().AttachmentCount();
  881. int sc = GetInventory().GetAttachmentSlotsCount() + GetMuzzleCount();
  882. if (ac > sc) sc = ac; // fix of some weapons which has 1 attachments but 0 slots...
  883. return sc;
  884. };
  885. PropertyModifiers GetPropertyModifierObject()
  886. {
  887. if (!m_PropertyModifierObject)
  888. {
  889. m_PropertyModifierObject = new PropertyModifiers(this);
  890. }
  891. return m_PropertyModifierObject;
  892. }
  893. void OnFire(int muzzle_index)
  894. {
  895. /*
  896. array<Man> players();
  897. GetGame().GetPlayers(players);
  898. Man root = GetHierarchyRootPlayer();
  899. if (!root)
  900. {
  901. return;
  902. }
  903. vector safePosition = root.GetPosition() + (root.GetDirection() * "0 1 0" * 3.0);
  904. Man other = null;
  905. foreach (auto player : players)
  906. {
  907. if (player != GetHierarchyRootPlayer())
  908. {
  909. player.SetPosition(safePosition);
  910. }
  911. }
  912. */
  913. m_BurstCount++;
  914. }
  915. void OnFireModeChange(int fireMode)
  916. {
  917. if ( !GetGame().IsDedicatedServer() )
  918. {
  919. EffectSound eff;
  920. if ( fireMode == 0 )
  921. eff = SEffectManager.PlaySound("Fire_Mode_Switch_Marked_Click_SoundSet", GetPosition());
  922. else
  923. eff = SEffectManager.PlaySound("Fire_Mode_Switch_Simple_Click_SoundSet", GetPosition());
  924. eff.SetAutodestroy(true);
  925. }
  926. ResetBurstCount();
  927. }
  928. void DelayedValidateAndRepair()
  929. {
  930. if (m_DelayedValidationTimer)
  931. {
  932. m_DelayedValidationTimer.Run(VALIDATE_DELAY , this, "ValidateAndRepair");
  933. }
  934. else
  935. {
  936. Error("[wpn] Weapon_Base::DelayedValidateAndRepair m_DelayedValidationTimer not initialized.");
  937. ValidateAndRepair();
  938. }
  939. }
  940. void ValidateAndRepair()
  941. {
  942. if ( m_fsm )
  943. m_fsm.ValidateAndRepair();
  944. }
  945. override void OnInventoryEnter(Man player)
  946. {
  947. m_PropertyModifierObject = null;
  948. if (GetGame().IsServer())
  949. {
  950. // The server knows about all of its attachments
  951. ValidateAndRepair();
  952. }
  953. else
  954. {
  955. // The client doesn't know it has attachments yet... give it a moment
  956. DelayedValidateAndRepair();
  957. }
  958. super.OnInventoryEnter(player);
  959. }
  960. override void OnInventoryExit(Man player)
  961. {
  962. m_PropertyModifierObject = null;
  963. super.OnInventoryExit(player);
  964. }
  965. override void EEItemAttached(EntityAI item, string slot_name)
  966. {
  967. super.EEItemAttached(item, slot_name);
  968. GetPropertyModifierObject().UpdateModifiers();
  969. }
  970. override void EEItemDetached(EntityAI item, string slot_name)
  971. {
  972. super.EEItemDetached(item, slot_name);
  973. GetPropertyModifierObject().UpdateModifiers();
  974. }
  975. override void EEItemLocationChanged(notnull InventoryLocation oldLoc, notnull InventoryLocation newLoc)
  976. {
  977. super.EEItemLocationChanged(oldLoc, newLoc);
  978. if (newLoc.GetType() == InventoryLocationType.HANDS)
  979. {
  980. PlayerBase player;
  981. if (newLoc.GetParent() && PlayerBase.CastTo(player, newLoc.GetParent()))
  982. {
  983. HumanCommandMove cm = player.GetCommand_Move();
  984. if (cm)
  985. {
  986. cm.SetMeleeBlock(false);
  987. }
  988. }
  989. }
  990. }
  991. override void OnItemLocationChanged(EntityAI old_owner, EntityAI new_owner)
  992. {
  993. /*
  994. // TODO(kumarjac): Solve this in code instead, OnItemLocationChanged is called too late.
  995. // Maybe extend an option for items to specify what attachments they must
  996. // be synchronized with? Moving to 'DelayedValidateAndRepair' works for now.
  997. int muzzles = GetMuzzleCount();
  998. for (int muzzleIdx = 0; muzzleIdx < muzzles; muzzleIdx++)
  999. {
  1000. Magazine mag = GetMagazine(muzzleIdx);
  1001. Print(mag);
  1002. if (!mag)
  1003. continue;
  1004. Print(mag.GetInventory());
  1005. if (!mag.GetInventory())
  1006. continue;
  1007. InventoryLocation invLoc = new InventoryLocation();
  1008. mag.GetInventory().GetCurrentInventoryLocation(invLoc);
  1009. GetGame().AddInventoryJunctureEx(null, this, invLoc, true, 1000);
  1010. }
  1011. */
  1012. super.OnItemLocationChanged(old_owner,new_owner);
  1013. // "resets" optics memory on optics
  1014. PlayerBase player;
  1015. if (PlayerBase.CastTo(player,old_owner))
  1016. {
  1017. player.SetReturnToOptics(false);
  1018. //optics item state reset
  1019. ItemOptics optics;
  1020. if (Class.CastTo(optics,GetAttachedOptics()))
  1021. {
  1022. player.SwitchOptics(optics,false);
  1023. }
  1024. }
  1025. if (old_owner != new_owner && PlayerBase.Cast(new_owner))
  1026. SetWasIronSight(true); // reset ironsight/optic default
  1027. HideWeaponBarrel(false);
  1028. }
  1029. override bool CanReleaseAttachment(EntityAI attachment)
  1030. {
  1031. if ( !super.CanReleaseAttachment( attachment ) )
  1032. return false;
  1033. Magazine mag = Magazine.Cast(attachment);
  1034. if (mag)
  1035. {
  1036. PlayerBase player = PlayerBase.Cast( GetHierarchyRootPlayer() );
  1037. if ( player )
  1038. {
  1039. if ( player.GetItemInHands() == this )
  1040. return true;
  1041. }
  1042. return false;
  1043. }
  1044. return true;
  1045. }
  1046. override bool CanRemoveFromHands(EntityAI parent)
  1047. {
  1048. if (IsIdle())
  1049. {
  1050. return true;
  1051. }
  1052. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " Weapon=" + this + " not in stable state=" + GetCurrentState().Type()); }
  1053. return false; // do not allow removal of weapon while weapon is busy
  1054. }
  1055. bool IsRemoteWeapon()
  1056. {
  1057. InventoryLocation il = new InventoryLocation;
  1058. if (GetInventory().GetCurrentInventoryLocation(il))
  1059. {
  1060. EntityAI parent = il.GetParent();
  1061. DayZPlayer dayzp = DayZPlayer.Cast(parent);
  1062. if (il.GetType() == InventoryLocationType.HANDS && dayzp)
  1063. {
  1064. bool remote = dayzp.GetInstanceType() == DayZPlayerInstanceType.INSTANCETYPE_REMOTE;
  1065. return remote;
  1066. }
  1067. }
  1068. return true;
  1069. }
  1070. void SyncEventToRemote(WeaponEventBase e)
  1071. {
  1072. DayZPlayer p = DayZPlayer.Cast(GetHierarchyParent());
  1073. if (p && p.GetInstanceType() == DayZPlayerInstanceType.INSTANCETYPE_SERVER)
  1074. {
  1075. ScriptRemoteInputUserData ctx = new ScriptRemoteInputUserData();
  1076. ctx.Write(INPUT_UDT_WEAPON_REMOTE_EVENT);
  1077. e.WriteToContext(ctx);
  1078. if (LogManager.IsWeaponLogEnable())
  1079. wpnDebugPrint("[wpnfsm] " + Object.GetDebugName(this) + " send 2 remote: sending e=" + e + " id=" + e.GetEventID() + " p=" + e.m_player + " m=" + e.m_magazine);
  1080. p.StoreInputForRemotes(ctx);
  1081. }
  1082. }
  1083. RecoilBase SpawnRecoilObject()
  1084. {
  1085. return new DefaultRecoil(this);
  1086. }
  1087. int GetWeaponSpecificCommand(int weaponAction, int subCommand)
  1088. {
  1089. return subCommand;
  1090. }
  1091. bool CanFire()
  1092. {
  1093. if (!IsChamberEmpty(GetCurrentMuzzle()) && !IsChamberFiredOut(GetCurrentMuzzle()) && !IsJammed() && !m_LiftWeapon && !IsDamageDestroyed())
  1094. return true;
  1095. return false;
  1096. }
  1097. bool CanEnterIronsights()
  1098. {
  1099. ItemOptics optic = GetAttachedOptics();
  1100. if (!optic)
  1101. return true;
  1102. return optic.HasWeaponIronsightsOverride();
  1103. }
  1104. //! Initializes DOF properties for weapon's ironsight/optics cameras
  1105. bool InitDOFProperties(out array<float> temp_array)
  1106. {
  1107. if (GetGame().ConfigIsExisting("cfgWeapons " + GetType() + " PPDOFProperties"))
  1108. {
  1109. GetGame().ConfigGetFloatArray("cfgWeapons " + GetType() + " PPDOFProperties", temp_array);
  1110. return true;
  1111. }
  1112. return false;
  1113. }
  1114. bool InitReliability(out array<float> reliability_array)
  1115. {
  1116. if (GetGame().ConfigIsExisting("cfgWeapons " + GetType() + " Reliability ChanceToJam"))
  1117. {
  1118. GetGame().ConfigGetFloatArray("cfgWeapons " + GetType() + " Reliability ChanceToJam", reliability_array);
  1119. return true;
  1120. }
  1121. return false;
  1122. }
  1123. //!gets weapon length from config for weaponlift raycast
  1124. bool InitWeaponLength()
  1125. {
  1126. if (ConfigIsExisting("WeaponLength"))
  1127. {
  1128. m_WeaponLength = ConfigGetFloat("WeaponLength");
  1129. return true;
  1130. }
  1131. m_WeaponLength = 0.8; //default value if not set in config; should not be zero
  1132. return false;
  1133. }
  1134. //!gets weapon vertical offset from config for weaponlift raycast
  1135. bool InitWeaponLiftCheckVerticalOffset()
  1136. {
  1137. if (ConfigIsExisting("WeaponLiftCheckVerticalOffset"))
  1138. {
  1139. m_WeaponLiftCheckVerticalOffset = ConfigGetFloat("WeaponLiftCheckVerticalOffset");
  1140. return true;
  1141. }
  1142. m_WeaponLiftCheckVerticalOffset = 0.0; //no offset by default
  1143. return false;
  1144. }
  1145. //!gets approximate weapon distance from shoulder from config
  1146. protected bool InitShoulderDistance()
  1147. {
  1148. if (ConfigIsExisting("ShoulderDistance"))
  1149. {
  1150. m_ShoulderDistance = ConfigGetFloat("ShoulderDistance");
  1151. return true;
  1152. }
  1153. m_ShoulderDistance = 0;
  1154. return false;
  1155. }
  1156. //!gets weapon obstruction distance from shoulder at which the weapon is fully obstructed
  1157. protected bool InitObstructionDistance()
  1158. {
  1159. if (ConfigIsExisting("ObstructionDistance"))
  1160. {
  1161. m_ObstructionDistance = ConfigGetFloat("ObstructionDistance");
  1162. return true;
  1163. }
  1164. m_ObstructionDistance = 0;
  1165. return false;
  1166. }
  1167. ref array<float> GetWeaponDOF()
  1168. {
  1169. return m_DOFProperties;
  1170. }
  1171. bool GetWasIronSight()
  1172. {
  1173. return m_WasIronSight;
  1174. }
  1175. // used to remember last used ironsight/optic state
  1176. void SetWasIronSight(bool state)
  1177. {
  1178. m_WasIronSight = state;
  1179. }
  1180. // lifting weapon on obstcles
  1181. bool LiftWeaponCheck(PlayerBase player)
  1182. {
  1183. Object object;
  1184. float obstruction;
  1185. return LiftWeaponCheckEx(player, obstruction, object);
  1186. }
  1187. /*!
  1188. Returns whether this weapon can use obstruction instead of weapon lift.
  1189. Implement simple conditions only (e.g. checking for attachments, weapon types, ...) to prevent possible desyncs.
  1190. \param obstructionValue The percentage of penetration into hit object
  1191. \param hitObject The object obstructing the weapon
  1192. */
  1193. bool UseWeaponObstruction(PlayerBase player, float obstructionValue, Object hitObject)
  1194. {
  1195. HumanMovementState ms = new HumanMovementState();
  1196. player.GetMovementState(ms);
  1197. #ifdef DIAG_DEVELOPER
  1198. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 2) // allow always
  1199. return true;
  1200. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 6) // neverEver
  1201. return false;
  1202. #endif
  1203. // Obstruction in prone does not really work well, the weapon has no physical room
  1204. // to move, so we instead always lift in such stance
  1205. if (ms.IsInProne() || ms.IsInRaisedProne())
  1206. {
  1207. return false;
  1208. }
  1209. // if ( m_ObstructionDistance != 0 && m_ObstructionDistance < 0.7 && ZombieBase.Cast( hitObject ) ) return true;
  1210. bool isDynamic;
  1211. bool isStatic;
  1212. if (hitObject)
  1213. {
  1214. isDynamic = dBodyIsDynamic(hitObject);
  1215. isStatic = !isDynamic;
  1216. }
  1217. #ifdef DIAG_DEVELOPER
  1218. // alwaysDynamic || alwaysDynamicNeverStatic
  1219. bool diagAlwaysDynamic = DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 3 || DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 5;
  1220. if (diagAlwaysDynamic && isDynamic) // alwaysDynamic
  1221. return true;
  1222. // neverStatic || alwaysDynamicNeverStatic
  1223. bool diagNeverStatic = DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 4 || DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 5;
  1224. if (diagNeverStatic && isStatic) // neverStatic
  1225. return false;
  1226. #endif
  1227. //CFGGAMEPLAY
  1228. EWeaponObstructionMode staticMode = CfgGameplayHandler.GetWeaponObstructionModeStatic();
  1229. EWeaponObstructionMode dynamicMode = CfgGameplayHandler.GetWeaponObstructionModeDynamic();
  1230. if (hitObject) // Can determine logic reliably
  1231. {
  1232. if ((isStatic && staticMode == EWeaponObstructionMode.DISABLED) || (isDynamic && dynamicMode == EWeaponObstructionMode.DISABLED))
  1233. {
  1234. return false;
  1235. }
  1236. else if ((isStatic && staticMode == EWeaponObstructionMode.ALWAYS) || (isDynamic && dynamicMode == EWeaponObstructionMode.ALWAYS))
  1237. {
  1238. return true;
  1239. }
  1240. }
  1241. else if (obstructionValue > 0) // With no hit we have to guess whether object was dynamic or static
  1242. {
  1243. // Allow obstruction if it was already going on (and it is allowed in either mode)
  1244. return staticMode != EWeaponObstructionMode.DISABLED && dynamicMode != EWeaponObstructionMode.DISABLED;
  1245. }
  1246. //!CFGGAMEPLAY
  1247. // Create a buffer between entering and leaving the lift to prevent
  1248. // continuous state changes while the value is near the edge. (hysteresis)
  1249. bool isLift = player.IsLiftWeapon();
  1250. if (isLift && obstructionValue > 0.9) // Retain lift while already lifted
  1251. {
  1252. return false;
  1253. }
  1254. if (!isLift && obstructionValue >= 1.0) // Enter lift while not lifted and obstructed enough
  1255. {
  1256. return false;
  1257. }
  1258. #ifdef DIAG_DEVELOPER // Keep this diag below all state conditions but above weapon type checks
  1259. if (DiagMenu.GetValue(DiagMenuIDs.WEAPON_FORCEALLOW_OBSTRUCTION) == 1) // allow conditionally
  1260. return true;
  1261. #endif
  1262. // Allow obstruction with weapons that have their distance defined, otherwise don't
  1263. return m_ObstructionDistance != 0;
  1264. }
  1265. //----------------------------------------------------------------------------------------
  1266. /*
  1267. Computes/fills the provided `dst` with aim offsets relevant for the provided `characterStance`.
  1268. Aiming angles are sampled as the normalized < -1.0, +1.0 > range.
  1269. */
  1270. protected void GetApproximateAimOffsets(Blend2DVector dst, int characterStance)
  1271. {
  1272. if (characterStance >= DayZPlayerConstants.STANCEIDX_RAISED)
  1273. characterStance -= DayZPlayerConstants.STANCEIDX_RAISED;
  1274. // All following values were set by inspecting the character with
  1275. // a weapon in-game and adjusting the offsets as such that with
  1276. // the weapon lift diagnostic enabled, the shapes would nearly
  1277. // perfectly overlap an equipped RIFLE.
  1278. if (characterStance == DayZPlayerConstants.STANCEIDX_CROUCH)
  1279. {
  1280. dst.Insert( 0.0, -1.0, " 0.16 0.22 -0.04"); // fully down
  1281. dst.Insert( 0.0, -0.5, " 0.14 0.13 0.00"); // halway down
  1282. dst.Insert( 0.0, 0.0, " 0.13 0.04 -0.02"); // forward
  1283. dst.Insert( 0.0, 0.5, " 0.13 0.01 -0.03"); // halfway up
  1284. dst.Insert( 0.0, 1.0, " 0.14 -0.01 -0.04"); // fully up
  1285. }
  1286. else if (characterStance == DayZPlayerConstants.STANCEIDX_PRONE)
  1287. {
  1288. dst.Insert( 0.0, -1.0, " 0.120 -0.080 -0.030"); // fully down
  1289. dst.Insert( 0.0, -0.5, " 0.120 -0.040 -0.040"); // halfway down
  1290. dst.Insert( 0.0, 0.0, " 0.120 0.010 -0.022");// forward
  1291. dst.Insert( 0.0, 0.5, " 0.120 -0.080 -0.050"); // halfway up
  1292. dst.Insert( 0.0, 1.0, " 0.120 -0.160 -0.130"); // fully up
  1293. // prone is very special, so there are some points mapped
  1294. // when aiming right and left (and up), to at least somewhat
  1295. // correspond with the actual character animation
  1296. dst.Insert( 0.3, 0.0, " 0.110 0.008 0.010");
  1297. dst.Insert( 0.5, 0.0, " 0.000 0.100 0.025");
  1298. dst.Insert( 0.8, 0.0, " 0.070 0.150 -0.014");
  1299. dst.Insert( 1.0, 0.0, " 0.140 -0.050 0.020");
  1300. dst.Insert(-0.3, 0.0, " 0.090 -0.100 -0.025");
  1301. dst.Insert(-0.5, 0.0, " 0.072 -0.064 -0.002");
  1302. dst.Insert(-0.9, 0.0, " 0.129 -0.080 0.015");
  1303. dst.Insert(-1.0, 0.0, " 0.140 -0.050 0.020");
  1304. dst.Insert( 0.5, 1.0, "-0.050 0.150 0.120");
  1305. dst.Insert( 1.0, 1.0, " 0.150 -0.035 0.030");
  1306. dst.Insert(-0.5, 1.0, " 0.050 -0.124 -0.040");
  1307. dst.Insert(-1.0, 1.0, " 0.150 -0.035 0.030");
  1308. }
  1309. else
  1310. {
  1311. dst.Insert( 0.0, -1.0, "0.13 0.14 0.082"); // fully down
  1312. dst.Insert( 0.0, -0.5, "0.13 0.05 0.048"); // halfway down
  1313. dst.Insert( 0.0, 0.0, "0.13 0.01 -0.008"); // forward
  1314. dst.Insert( 0.0, 0.5, "0.13 0.00 -0.015"); // halfway up
  1315. dst.Insert( 0.0, 1.0, "0.13 -0.04 -0.016"); // fully up
  1316. }
  1317. }
  1318. //----------------------------------------------------------------------------------------
  1319. /*
  1320. Computes approximate offset during movement for this weapon.
  1321. */
  1322. protected vector GetApproximateMovementOffset(vector localVelocity, int characterStance, float lean, float ud11, float lr11)
  1323. {
  1324. vector offset;
  1325. if (lean != 0)
  1326. {
  1327. const float LEAN_VERT_OFFSET = -0.1;
  1328. const float LEAN_HORIZ_OFFSET_L = 0;
  1329. const float LEAN_HORIZ_OFFSET_R = 0.01;
  1330. float aimStraightWeight = 1.0 - Math.AbsFloat(ud11); // 1 when aiming forward
  1331. float leanOffset = lean * aimStraightWeight;
  1332. offset += Vector( leanOffset * Math.Lerp(LEAN_HORIZ_OFFSET_L, LEAN_HORIZ_OFFSET_R, lean * 0.5 + 0.5), leanOffset * LEAN_VERT_OFFSET, 0);
  1333. }
  1334. float maxVelocity = Math.Max( Math.AbsFloat(localVelocity[0]), Math.AbsFloat(localVelocity[2]) );
  1335. float peakVelocity = 0.5;
  1336. float moveAmount01 = Math.Clamp(maxVelocity / peakVelocity, 0.0, 1.0);
  1337. if (moveAmount01 != 0.0)
  1338. {
  1339. vector moveOffset = "0 -0.2 -0.1";
  1340. float ud01 = (ud11 * 0.5) + 0.5;
  1341. float aimWeight = Math.Clamp(1.0 - (ud01 * 2), 0, 1);
  1342. // The issue is only apparent when looking down and the 2nd power seems
  1343. // to follow the actual visual relatively accurately
  1344. float moveWeight = moveAmount01 * Math.Pow(aimWeight, 2.0);
  1345. offset = offset + (moveWeight * moveOffset);
  1346. }
  1347. return offset;
  1348. }
  1349. //----------------------------------------------------------------------------------------
  1350. /*!
  1351. Update provided `start` position and lift check `direction` to include appproximated character state.
  1352. \param start Ray start position, to be modified
  1353. \param direction Ray direction
  1354. */
  1355. protected void ApproximateWeaponLiftTransform(inout vector start, inout vector direction, HumanMovementState hms, HumanInputController hic, HumanCommandWeapons hcw, HumanCommandMove hcm, vector localVelocity = "0 0 0")
  1356. {
  1357. // Construct stable trasformation matrix that somewhat aligns with the weapon transform,
  1358. // without actually using the weapon as reference - the weapon will move during the lift/obstruction
  1359. // in more than 1 axis and is therefore not realiable source of truth.
  1360. vector resTM[4];
  1361. resTM[0] = Vector(direction[0], 0, direction[2]).Normalized();
  1362. resTM[0] = vector.RotateAroundZeroDeg(resTM[0], vector.Up, 90);
  1363. resTM[2] = direction;
  1364. resTM[1] = resTM[2] * resTM[0];
  1365. resTM[3] = start;
  1366. // Approximate the roll angle of leaning
  1367. float leanAngle = hms.m_fLeaning * 35;
  1368. vector rotTM[3];
  1369. float xAimHandsOffset = hcw.GetAimingHandsOffsetLR();
  1370. float yAimHandsOffset = hcw.GetAimingHandsOffsetUD();
  1371. Math3D.YawPitchRollMatrix( Vector(xAimHandsOffset , yAimHandsOffset, leanAngle), rotTM );
  1372. Math3D.MatrixMultiply3(resTM, rotTM, resTM);
  1373. // Draw relative transformation matrix diagnostic
  1374. #ifndef SERVER
  1375. #ifdef DIAG_DEVELOPER
  1376. PluginDiagMenuClient.GetWeaponLiftDiag().Data().SetTransform(resTM);
  1377. #endif
  1378. #endif
  1379. // Compute normalized aiming angles
  1380. float udAngle = hcw.GetBaseAimingAngleUD();
  1381. float lrAngle = hcw.GetBaseAimingAngleLR();
  1382. float ud01 = Math.InverseLerp(DayZPlayerCamera1stPerson.CONST_UD_MIN, DayZPlayerCamera1stPerson.CONST_UD_MAX, udAngle); // 0-1
  1383. float ud11 = Math.Clamp((ud01 * 2) - 1, -1, 1); // -1, 1
  1384. float lr01 = Math.InverseLerp(DayZPlayerCamera1stPerson.CONST_LR_MIN, DayZPlayerCamera1stPerson.CONST_LR_MAX, lrAngle); // 0-1
  1385. float lr11 = Math.Clamp((lr01 * 2) - 1, -1, 1);
  1386. #ifndef SERVER
  1387. #ifdef DIAG_DEVELOPER
  1388. PluginDiagMenuClient.GetWeaponLiftDiag().Data().SetAimAngles(udAngle, lrAngle, ud11, lr11);
  1389. #endif
  1390. #endif
  1391. // Fetch approximate aim offset position based on current state
  1392. Blend2DVector aimOffsets = new Blend2DVector();
  1393. GetApproximateAimOffsets(aimOffsets, hms.m_iStanceIdx);
  1394. vector offset = aimOffsets.Blend(lr11, ud11);
  1395. // Apply height offset if any is defined
  1396. if (m_WeaponLiftCheckVerticalOffset != 0)
  1397. {
  1398. offset[1] = offset[1] + m_WeaponLiftCheckVerticalOffset;
  1399. }
  1400. // Approximate the shift caused by movement. There is an enormous shift when aiming
  1401. // downwards, creating insane shift that we will compensate by shifting the offset
  1402. // based on the movement and aiming angle:
  1403. vector moveOffset = GetApproximateMovementOffset(localVelocity, hms.m_iStanceIdx, hms.m_fLeaning, ud11, lr11);
  1404. offset += moveOffset;
  1405. // While changing stances the weapon moves slightly forward and although this may
  1406. // cause some unwanted lifts ocasionally, it should prevent some unwanted clipping
  1407. if (hcm.IsChangingStance())
  1408. {
  1409. offset[2] = offset[2] + 0.05;
  1410. }
  1411. offset = offset.InvMultiply3(rotTM);
  1412. // Finally use the computed offset as the start position.
  1413. start = offset.Multiply4(resTM);
  1414. }
  1415. //----------------------------------------------------------------------------------------
  1416. //! Approximate `ObstructionDistance` for weapons with no configuration. Returned length doesn't account for attachments.
  1417. private float ApproximateBaseObstructionLength()
  1418. {
  1419. float approximateLength = Math.Max(0, m_WeaponLength / 1.5) * m_WeaponLength;
  1420. return m_ShoulderDistance + approximateLength;
  1421. }
  1422. //----------------------------------------------------------------------------------------
  1423. /*!
  1424. Perform weapon obstruction check by the provided `player`.
  1425. \param player The player to perform the check.
  1426. \param outObstruction Result obstruction value [0 .. 1] or > 1 when out of range and needs lift instead
  1427. \param outHitObject Object that was hit (if any)
  1428. \return True whenever weapon should be lifted or obstruted. (See also Weapon_Base.UseWeaponObstruction)
  1429. */
  1430. bool LiftWeaponCheckEx(PlayerBase player, out float outObstruction, out Object outHitObject)
  1431. {
  1432. bool wasLift = m_LiftWeapon;
  1433. vector lastLiftPosition = m_LastLiftPosition;
  1434. m_LiftWeapon = false;
  1435. // [14.2.2025]
  1436. // The following selection check should be *unused* for some time now.
  1437. // Leaving commented out for future reference, should the removal have unforseen consequences.
  1438. // if ( HasSelection("Usti hlavne") )
  1439. // return false;
  1440. if (!player)
  1441. {
  1442. #ifndef SERVER
  1443. #ifdef DIAG_DEVELOPER
  1444. PluginDiagMenuClient.GetWeaponLiftDiag().Reset();
  1445. #endif
  1446. #endif
  1447. Print("Error: No weapon owner for LiftWeaponCheckEx, returning.");
  1448. return false;
  1449. }
  1450. // Obstruction can only occur if the weapon is rasied
  1451. HumanMovementState movementState = new HumanMovementState();
  1452. player.GetMovementState(movementState);
  1453. if (!movementState.IsRaised())
  1454. return false;
  1455. // Suppress weapon obstruction during melee attack preventing state inconsistency
  1456. if (movementState.m_CommandTypeId == DayZPlayerConstants.COMMANDID_MELEE || movementState.m_CommandTypeId == DayZPlayerConstants.COMMANDID_MELEE2)
  1457. {
  1458. #ifndef SERVER
  1459. #ifdef DIAG_DEVELOPER
  1460. PluginDiagMenuClient.GetWeaponLiftDiag().Reset();
  1461. #endif
  1462. #endif
  1463. return false;
  1464. }
  1465. // If possible use aiming angles instead as these will work consistently
  1466. // and independently of any cameras, etc.
  1467. vector direction;
  1468. HumanInputController hic = player.GetInputController();
  1469. HumanCommandWeapons hcw = player.GetCommandModifier_Weapons();
  1470. HumanCommandMove hcm = player.GetCommand_Move();
  1471. if (hcw)
  1472. {
  1473. vector yawPitchRoll = Vector(
  1474. hcw.GetBaseAimingAngleLR() + player.GetOrientation()[0],
  1475. hcw.GetBaseAimingAngleUD(),
  1476. 0.0);
  1477. float xAimHandsOffset = hcw.GetAimingHandsOffsetLR();
  1478. float yAimHandsOffset = hcw.GetAimingHandsOffsetUD();
  1479. yawPitchRoll[0] = yawPitchRoll[0] + xAimHandsOffset;
  1480. yawPitchRoll[1] = Math.Clamp( yawPitchRoll[1] + yAimHandsOffset, DayZPlayerCamera1stPerson.CONST_UD_MIN, DayZPlayerCamera1stPerson.CONST_UD_MAX );
  1481. direction = yawPitchRoll.AnglesToVector();
  1482. }
  1483. else // Fallback to previously implemented logic
  1484. {
  1485. // freelook raycast
  1486. if (hic.CameraIsFreeLook())
  1487. {
  1488. if (player.m_DirectionToCursor != vector.Zero)
  1489. {
  1490. direction = player.m_DirectionToCursor;
  1491. }
  1492. // if player raises weapon in freelook
  1493. else
  1494. {
  1495. direction = MiscGameplayFunctions.GetHeadingVector(player);
  1496. }
  1497. }
  1498. else
  1499. {
  1500. direction = GetGame().GetCurrentCameraDirection(); // exception for freelook. Much better this way!
  1501. }
  1502. }
  1503. // Reference bone to perform checks from
  1504. vector start;
  1505. int boneIdx = player.GetBoneIndexByName("Neck");
  1506. if (boneIdx == -1)
  1507. {
  1508. start = player.GetPosition()[1] + 1.5;
  1509. }
  1510. else
  1511. {
  1512. start = player.GetBonePositionWS(boneIdx);
  1513. }
  1514. // Update the ray origin and direction with approximations of character state
  1515. vector velocity = GetVelocity(player);
  1516. velocity = player.VectorToLocal(velocity);
  1517. ApproximateWeaponLiftTransform(start, direction, movementState, hic, hcw, hcm, velocity);
  1518. float effectiveAttachmentLength = GetEffectiveAttachmentLength();
  1519. // weapon length including effective attachments
  1520. float weaponLength = m_WeaponLength + effectiveAttachmentLength;
  1521. // distance of point from 'shoulder' (start) from which the weapon length should be computed,
  1522. // pistols have the start point further, near the character wrist whereas longer rifles, typically
  1523. // the ones with buttstocks have the start point at 0 distance, ie. directly at shoulder
  1524. float weaponStartDist = m_ShoulderDistance;
  1525. float weaponEndDist = weaponStartDist + weaponLength;
  1526. // Weapon start and end positions as an offset from the character shoulder
  1527. vector weaponStart = start + (weaponStartDist * direction);
  1528. vector weaponEnd = start + (weaponEndDist * direction);
  1529. // distance of point from 'shoulder' (start) at which the weapon is "fully obstructed", i.e.
  1530. // needs to be lifted to be able to be physically fit wherever it is. Shifted by effective
  1531. // attachment length to account for e.g. suppressors that make the weapon "longer".
  1532. // Defaults to FLT_MAX if no value is configured, effectivelly always lifting weapon instead of obstructing.
  1533. float baseObstructionLength = m_ObstructionDistance;
  1534. if (baseObstructionLength==0)
  1535. {
  1536. baseObstructionLength = ApproximateBaseObstructionLength();
  1537. }
  1538. float weaponObstructionDist = baseObstructionLength + effectiveAttachmentLength;
  1539. float rayRadius = 0.02;
  1540. #ifndef SERVER
  1541. #ifdef DIAG_DEVELOPER // Retrieve possible override
  1542. float overrideObstDist = PluginDiagMenuClient.GetWeaponLiftDiag().Data().m_ObstructionDistance;
  1543. PluginDiagMenuClient.GetWeaponLiftDiag().Data().SetWeaponRayParams(start, direction, weaponStartDist, weaponEndDist, effectiveAttachmentLength, m_ObstructionDistance, weaponObstructionDist, rayRadius);
  1544. weaponObstructionDist = overrideObstDist;
  1545. rayRadius = PluginDiagMenuClient.GetWeaponLiftDiag().Data().m_RayRadiusOverride;
  1546. #endif
  1547. #endif
  1548. // Extend the raycast range by 30 cm to allow checking for intersections even
  1549. // under shallow angles and other odd cases
  1550. float rayEndDist = weaponEndDist + 0.30;
  1551. vector rayEnd = start + rayEndDist * direction;
  1552. // Prepare raycast params and perform the cast in fire geo
  1553. RaycastRVParams rayParm = new RaycastRVParams(start, rayEnd, player, rayRadius);
  1554. rayParm.flags = CollisionFlags.ALLOBJECTS;
  1555. rayParm.type = ObjIntersect.Fire;
  1556. RaycastRVResult hitResult;
  1557. float hitFraction;
  1558. float hitDist;
  1559. array<ref RaycastRVResult> results = {};
  1560. if (!DayZPhysics.RaycastRVProxy(rayParm, results) || results.Count() == 0)
  1561. {
  1562. hitFraction = 0;
  1563. }
  1564. else
  1565. {
  1566. // In case of multiple results, find the best (nearest) match, RaycastRVProxy doesn't guarantee any sensible order
  1567. int numRes = results.Count();
  1568. if (numRes == 1)
  1569. {
  1570. hitResult = results[0];
  1571. }
  1572. else
  1573. {
  1574. int bi = -1;
  1575. float maxDist = float.MAX;
  1576. for (int i = 0, nr = results.Count(); i < nr; ++i)
  1577. {
  1578. float sqDist = vector.DistanceSq(results[i].pos, weaponStart);
  1579. if (sqDist < maxDist)
  1580. {
  1581. maxDist = sqDist;
  1582. bi = i;
  1583. }
  1584. }
  1585. hitResult = results[bi];
  1586. }
  1587. if (LiftWeaponRaycastResultCheck(hitResult))
  1588. {
  1589. float len0 = (hitResult.pos - start).Length();
  1590. float len1 = (weaponEnd - start).Length(); // Do not include 'rayEnd' - pretend as if the computation happened in <start, weaponEnd>
  1591. if (len0 <= 0 || len1 <= 0)
  1592. {
  1593. hitFraction = 1;
  1594. }
  1595. else
  1596. {
  1597. hitFraction = len0 / len1;
  1598. }
  1599. hitDist = hitFraction * weaponEndDist;
  1600. }
  1601. else
  1602. {
  1603. hitFraction = 0;
  1604. hitDist = 0;
  1605. }
  1606. }
  1607. #ifndef SERVER
  1608. #ifdef DIAG_DEVELOPER // isect diag
  1609. PluginDiagMenuClient.GetWeaponLiftDiag().Data().SetIntersectionParams(hitResult, hitFraction, hitDist);
  1610. PluginDiagMenuClient.GetWeaponLiftDiag().Data().SetLastPosition(lastLiftPosition);
  1611. #endif // !isect diag
  1612. #endif
  1613. // Start by assuming that we want to retain state
  1614. bool wantsLift = wasLift;
  1615. // Sq. distance of weapon movement required to trigger lift (in)
  1616. const float inThreshold = 0.002;
  1617. // Sq. distance of weapon movement required to trigger lift (out)
  1618. const float outThreshold = 0.003;
  1619. const float noIsctOutThreshold = 0.01;
  1620. // Max num of ticks with no hit for which hysteresis will persist
  1621. // value chosen by iteration, should be approx 0.333s
  1622. const int maxNumMissedTicks = 10;
  1623. // Min angle in degrees change from last lift to stop lifting
  1624. // Base threshold of 0.75 degrees + 0.6 degrees per meter of weapon length
  1625. float angleThreshold = 0.75 + Math.Clamp( m_WeaponLength * 0.6, 0, 1.5 );
  1626. // Update state when a hit is registered
  1627. if (hitFraction != 0)
  1628. {
  1629. vector v1 = hitResult.pos - weaponEnd;
  1630. vector v2 = hitResult.pos - rayEnd;
  1631. float d = vector.Dot(v1, v2);
  1632. // But leave some threshold where previous state is kept
  1633. // to prevent excessive switches from occuring
  1634. if (!wasLift && d > inThreshold)
  1635. {
  1636. wantsLift = true;
  1637. }
  1638. else if (wasLift && d < -outThreshold)
  1639. {
  1640. wantsLift = false;
  1641. }
  1642. m_LastLiftPosition = hitResult.pos;
  1643. m_LastLiftHit = player.GetSimulationTimeStamp();
  1644. }
  1645. else
  1646. {
  1647. // With no hit and no previous lift
  1648. if (lastLiftPosition == vector.Zero)
  1649. {
  1650. wantsLift = false;
  1651. m_LastLiftPosition = vector.Zero;
  1652. }
  1653. // See if previous hit wasn't very close to our current position,
  1654. // in which case simply don't lift the weapon
  1655. else
  1656. {
  1657. vector v3 = (lastLiftPosition - start).Normalized();
  1658. vector v4 = (weaponEnd-start).Normalized();
  1659. float d2 = vector.Dot(v3, v4);
  1660. // no isect, angle delta check
  1661. if (Math.Acos(d2) > (angleThreshold * Math.DEG2RAD)) // if relative angle is > x degree, stop lifting
  1662. {
  1663. wantsLift = false;
  1664. m_LastLiftPosition = vector.Zero;
  1665. }
  1666. // no isect, distance check
  1667. else
  1668. {
  1669. float d3 = vector.Dot( lastLiftPosition - weaponEnd, (start-weaponEnd).Normalized() );
  1670. if (d3 < -noIsctOutThreshold)
  1671. {
  1672. wantsLift = false;
  1673. m_LastLiftPosition = vector.Zero;
  1674. }
  1675. float lastObstruction = hcw.GetWeaponObstruction();
  1676. // fallback in case offending object disappears or moves
  1677. int timeSinceHit = player.GetSimulationTimeStamp() - m_LastLiftHit;
  1678. if (timeSinceHit > maxNumMissedTicks)
  1679. {
  1680. wantsLift = false;
  1681. m_LastLiftPosition = vector.Zero;
  1682. }
  1683. else if (wantsLift && m_LastLiftPosition != vector.Zero) // pretended hit to retain obstruction in this very tight edge case
  1684. {
  1685. float l0 = (m_LastLiftPosition - start).Length();
  1686. float l1 = (weaponEnd - start).Length();
  1687. if (l0 <= 0 || l1 <= 0)
  1688. {
  1689. hitFraction = 1;
  1690. }
  1691. else
  1692. {
  1693. hitFraction = l0 / l1;
  1694. }
  1695. hitDist = hitFraction * weaponEndDist;
  1696. }
  1697. }
  1698. }
  1699. }
  1700. // lift is desired
  1701. if (wantsLift)
  1702. {
  1703. // Remap the hit distance into the <obstruction, weaponWithAttachmentLength> range as 0..1 (and beyond)
  1704. float begDist = weaponObstructionDist;
  1705. float endDist = weaponStartDist + weaponLength;
  1706. float obstFraction;
  1707. if (begDist < endDist)
  1708. obstFraction = Math.InverseLerp( begDist, endDist, hitDist );
  1709. if (hitResult)
  1710. outHitObject = hitResult.obj;
  1711. outObstruction = 1.0 - obstFraction;
  1712. m_LiftWeapon = true;
  1713. return true;
  1714. }
  1715. return false;
  1716. }
  1717. /*!
  1718. Recomputes the provided `obstruction01` value typically returned by `LiftWeaponCheckEx`
  1719. from the [0 ... 1] range to distance in meters the weapon penetrates the obstacle.
  1720. \param obstruction01 Obstruction progress
  1721. \return Penetration depth in meters
  1722. */
  1723. float GetObstructionPenetrationDistance(float obstruction01)
  1724. {
  1725. float baseObstructionLength = m_ObstructionDistance;
  1726. if (baseObstructionLength==0)
  1727. {
  1728. baseObstructionLength = ApproximateBaseObstructionLength();
  1729. }
  1730. float effectiveAttachmentLength = GetEffectiveAttachmentLength();
  1731. float weaponEnd = m_ShoulderDistance + m_WeaponLength + effectiveAttachmentLength;
  1732. return weaponEnd - Math.Lerp(weaponEnd, baseObstructionLength + effectiveAttachmentLength, obstruction01);
  1733. }
  1734. //! Return whether provided material triggers weapon lift (true) or not (false).
  1735. bool LiftWeaponRaycastResultCheck(notnull RaycastRVResult res)
  1736. {
  1737. return res.surface.IsSolid();
  1738. }
  1739. //! Returns effective length of attachments that influence total weapon length
  1740. float GetEffectiveAttachmentLength()
  1741. {
  1742. ItemBase attachment;
  1743. if (HasBayonetAttached())
  1744. {
  1745. int bayonetIndex = GetBayonetAttachmentIdx();
  1746. attachment = ItemBase.Cast(GetInventory().FindAttachment(bayonetIndex));
  1747. }
  1748. else
  1749. {
  1750. attachment = GetAttachedSuppressor();
  1751. }
  1752. if (attachment)
  1753. {
  1754. return Math.Max(attachment.m_ItemModelLength + attachment.m_ItemAttachOffset, 0);
  1755. }
  1756. else
  1757. {
  1758. return 0;
  1759. }
  1760. }
  1761. void SetSyncJammingChance( float jamming_chance )
  1762. {
  1763. m_ChanceToJamSync = jamming_chance;
  1764. }
  1765. /**@fn EjectCartridge
  1766. * @brief unload bullet from chamber or internal magazine
  1767. *
  1768. * @NOTE: EjectCartridge = GetCartridgeInfo + PopCartridge
  1769. *
  1770. * @param[in] muzzleIndex
  1771. * @param[out] ammoDamage \p damage of the ammo
  1772. * @param[out] ammoTypeName \p type name of the ejected ammo
  1773. * @return true if bullet removed from chamber
  1774. **/
  1775. bool EjectCartridge(int muzzleIndex, out float ammoDamage, out string ammoTypeName)
  1776. {
  1777. if (IsChamberEjectable(muzzleIndex))
  1778. {
  1779. if (PopCartridgeFromChamber(muzzleIndex, ammoDamage, ammoTypeName))
  1780. return true;
  1781. }
  1782. else if (GetInternalMagazineCartridgeCount(muzzleIndex) > 0)
  1783. {
  1784. if (PopCartridgeFromInternalMagazine(muzzleIndex, ammoDamage, ammoTypeName))
  1785. return true;
  1786. }
  1787. return false;
  1788. }
  1789. bool CopyWeaponStateFrom(notnull Weapon_Base src)
  1790. {
  1791. float damage = 0.0;
  1792. string type;
  1793. for (int mi = 0; mi < src.GetMuzzleCount(); ++mi)
  1794. {
  1795. if (!src.IsChamberEmpty(mi))
  1796. {
  1797. if (src.GetCartridgeInfo(mi, damage, type))
  1798. {
  1799. PushCartridgeToChamber(mi, damage, type);
  1800. }
  1801. }
  1802. for (int ci = 0; ci < src.GetInternalMagazineCartridgeCount(mi); ++ci)
  1803. {
  1804. if (src.GetInternalMagazineCartridgeInfo(mi, ci, damage, type))
  1805. {
  1806. PushCartridgeToInternalMagazine(mi, damage, type);
  1807. }
  1808. }
  1809. }
  1810. int dummy_version = int.MAX;
  1811. PlayerBase parentPlayer = PlayerBase.Cast(src.GetHierarchyRootPlayer());
  1812. if (!parentPlayer)
  1813. dummy_version -= 1;
  1814. ScriptReadWriteContext ctx = new ScriptReadWriteContext;
  1815. src.OnStoreSave(ctx.GetWriteContext());
  1816. OnStoreLoad(ctx.GetReadContext(), dummy_version);
  1817. return true;
  1818. }
  1819. //! attachment helpers (firearm melee)
  1820. override void SetBayonetAttached(bool pState, int slot_idx = -1)
  1821. {
  1822. m_BayonetAttached = pState;
  1823. m_BayonetAttachmentIdx = slot_idx;
  1824. }
  1825. override bool HasBayonetAttached()
  1826. {
  1827. return m_BayonetAttached;
  1828. }
  1829. override int GetBayonetAttachmentIdx()
  1830. {
  1831. return m_BayonetAttachmentIdx;
  1832. }
  1833. override void SetButtstockAttached(bool pState, int slot_idx = -1)
  1834. {
  1835. m_ButtstockAttached = pState;
  1836. m_ButtstockAttachmentIdx = slot_idx;
  1837. }
  1838. override bool HasButtstockAttached()
  1839. {
  1840. return m_ButtstockAttached;
  1841. }
  1842. override int GetButtstockAttachmentIdx()
  1843. {
  1844. return m_ButtstockAttachmentIdx;
  1845. }
  1846. void HideWeaponBarrel(bool state)
  1847. {
  1848. if ( !GetGame().IsDedicatedServer() )//hidden for client only
  1849. {
  1850. ItemOptics optics = GetAttachedOptics();
  1851. if ( optics && !optics.AllowsDOF() && m_weaponHideBarrelIdx != -1 )
  1852. {
  1853. SetSimpleHiddenSelectionState(m_weaponHideBarrelIdx,!state);
  1854. }
  1855. }
  1856. }
  1857. void ShowMagazine()
  1858. {
  1859. if (m_magazineSimpleSelectionIndex > -1)
  1860. SetSimpleHiddenSelectionState(m_magazineSimpleSelectionIndex,1);
  1861. else
  1862. SelectionMagazineShow();
  1863. }
  1864. void HideMagazine()
  1865. {
  1866. if (m_magazineSimpleSelectionIndex > -1)
  1867. SetSimpleHiddenSelectionState(m_magazineSimpleSelectionIndex,0);
  1868. else
  1869. SelectionMagazineHide();
  1870. }
  1871. override EntityAI ProcessMeleeItemDamage(int mode = 0)
  1872. {
  1873. EntityAI attachment;
  1874. switch (mode)
  1875. {
  1876. case 0:
  1877. super.ProcessMeleeItemDamage();
  1878. break;
  1879. case 1:
  1880. attachment = GetInventory().FindAttachment(m_ButtstockAttachmentIdx);
  1881. break;
  1882. case 2:
  1883. attachment = GetInventory().FindAttachment(m_BayonetAttachmentIdx);
  1884. break;
  1885. default:
  1886. super.ProcessMeleeItemDamage();
  1887. break;
  1888. }
  1889. if (attachment)
  1890. {
  1891. attachment.ProcessMeleeItemDamage();
  1892. return attachment;
  1893. }
  1894. return this;
  1895. }
  1896. bool IsShowingChamberedBullet()
  1897. {
  1898. return true;
  1899. }
  1900. int GetBurstCount()
  1901. {
  1902. return m_BurstCount;
  1903. }
  1904. void ResetBurstCount()
  1905. {
  1906. m_BurstCount = 0;
  1907. }
  1908. override void SetActions()
  1909. {
  1910. super.SetActions();
  1911. AddAction(FirearmActionUnjam);
  1912. AddAction(FirearmActionAttachMagazine);
  1913. AddAction(FirearmActionLoadBullet);
  1914. AddAction(FirearmActionMechanicManipulate);
  1915. AddAction(ActionTurnOnWeaponFlashlight);
  1916. AddAction(ActionTurnOffWeaponFlashlight);
  1917. AddAction(FirearmActionAttachMagazineQuick); // Easy reload
  1918. AddAction(FirearmActionLoadBulletQuick); // Easy reload
  1919. }
  1920. override bool CanBeUsedForSuicide()
  1921. {
  1922. if (!ConfigGetBool("isSuicideWeapon"))
  1923. return false;
  1924. return super.CanBeUsedForSuicide();
  1925. }
  1926. //Debug menu Spawn Ground Special
  1927. override void OnDebugSpawn()
  1928. {
  1929. SpawnAmmo("", SAMF_DEFAULT);
  1930. }
  1931. bool AddJunctureToAttachedMagazine(PlayerBase player, int timeoutMS)
  1932. {
  1933. Magazine mag = GetMagazine(GetCurrentMuzzle());
  1934. InventoryLocation il = new InventoryLocation();
  1935. if (mag)
  1936. {
  1937. return GetGame().AddInventoryJunctureEx(player, mag, il, false, timeoutMS);
  1938. }
  1939. return true;
  1940. }
  1941. void ClearJunctureToAttachedMagazine(PlayerBase player)
  1942. {
  1943. Magazine mag = GetMagazine(GetCurrentMuzzle());
  1944. if (mag)
  1945. {
  1946. GetGame().ClearJunctureEx(player, mag);
  1947. }
  1948. }
  1949. void SetNextWeaponMode(int muzzleIndex)
  1950. {
  1951. SetNextMuzzleMode(muzzleIndex);
  1952. }
  1953. // CoolDown
  1954. void SetCoolDown( float coolDownTime )
  1955. {
  1956. m_coolDownTime = coolDownTime;
  1957. }
  1958. void UpdateCoolDown( float dt ) { m_coolDownTime -= dt; }
  1959. bool IsCoolDown()
  1960. {
  1961. return m_coolDownTime > 0;
  1962. }
  1963. // If there are bullet in attached / internal magazine then bullet must be in chamber also
  1964. bool MustBeChambered(int muzzleIndex)
  1965. {
  1966. return false;
  1967. }
  1968. #ifdef TEST_WEAPON_SYSNC_REPAIR
  1969. void SetSyncStable(bool value)
  1970. {
  1971. m_SyncStable = value;
  1972. if (!value)
  1973. {
  1974. m_SyncStableTime = GetGame().GetTickTime();
  1975. }
  1976. }
  1977. bool IsSyncStable()
  1978. {
  1979. return m_SyncStable;
  1980. }
  1981. #endif
  1982. };