Arma Reforger Explorer  1.1.0.42
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
SCR_CharacterDamageManagerComponent.c
Go to the documentation of this file.
1 enum ECharacterHitZoneGroup : EHitZoneGroup
2 {
3  HEAD = 10,
4  UPPERTORSO = 20,
5  LOWERTORSO = 30,
6  LEFTARM = 40,
7  RIGHTARM = 50,
8  LEFTLEG = 60,
9  RIGHTLEG = 70,
10 }
11 
12 //------------------------------------------------------------------------------------------------
13 class SCR_CharacterDamageManagerComponentClass: SCR_DamageManagerComponentClass
14 {
15 };
16 
17 //------------------------------------------------------------------------------------------------
18 class SCR_CharacterDamageManagerComponent : SCR_DamageManagerComponent
19 {
20  // 1000 ms timer for bloody clothes update
21  static const int BLOOD_CLOTHES_UPDATE_PERIOD = 1000;
22 
23  // bleeding rate multiplier after death - used to stop particles sooner
24  const float DEATH_BLEEDOUT_SCALE = 4;
25 
26  // Physics variables
27  protected float m_fHighestContact;
28  protected float m_fMinImpulse;
29 
30  // Static array for all limbs
31  static ref array<ECharacterHitZoneGroup> LIMB_GROUPS;
32  static const ref array<ECharacterHitZoneGroup> EXTREMITY_LIMB_GROUPS = {ECharacterHitZoneGroup.LEFTARM, ECharacterHitZoneGroup.RIGHTARM, ECharacterHitZoneGroup.LEFTLEG, ECharacterHitZoneGroup.RIGHTLEG};
33 
34  //replicated arrays for clients
35  protected ref array<ECharacterHitZoneGroup> m_aTourniquettedGroups;
36  protected ref array<ECharacterHitZoneGroup> m_aSalineBaggedGroups;
37  protected ref array<int> m_aBeingHealedGroup;
38  protected ref array<float> m_aGroupBleedingRates;
39 
40  protected ref map<SCR_CharacterHitZone, ref SCR_ArmoredClothItemData> m_mClothItemDataMap;
41  protected SCR_CharacterBloodHitZone m_pBloodHitZone;
42  protected ref array<HitZone> m_aBleedingHitZones;
43  protected ref map<HitZone, ParticleEffectEntity> m_mBleedingParticles;
44  protected SCR_CharacterResilienceHitZone m_pResilienceHitZone;
45  protected SCR_CharacterHeadHitZone m_pHeadHitZone;
46 
47  // audio
48  protected SCR_CommunicationSoundComponent m_CommunicationSound;
49 
50  static protected SCR_GameModeHealthSettings s_HealthSettings;
51 
52  protected bool m_bDOTScaleChangedByGM;
53  protected bool m_bRegenScaleChangedByGM;
54  protected bool m_bUnconsciousnessSettingsChangedByGM;
55  protected bool m_bOverrideCharacterMedical;
56 
57  // TODO: Move these attributes to prefab data to save some memory
58  [Attribute(defvalue: "", uiwidget: UIWidgets.ResourceNamePicker, desc: "Bleeding particle effect", params: "ptc", precision: 3, category: "Bleeding")]
59  protected ResourceName m_sBleedingParticle;
60 
61  [Attribute(defvalue: "0.5", uiwidget: UIWidgets.Slider, desc: "Bleeding particle effect rate scale", params: "0 5 0.001", precision: 3, category: "Bleeding Mode")]
62  protected float m_fBleedingParticleRateScale;
63 
64  [Attribute(defvalue: "1", uiwidget: UIWidgets.Slider, desc: "Character bleeding rate multiplier", params: "0 5 0.001", precision: 3, category: "Bleeding")]
65  protected float m_fDOTScale;
66 
67  [Attribute(defvalue: "1", uiwidget: UIWidgets.Slider, desc: "Character regeneration rate multiplier", params: "0 5 0.001", precision: 3, category: "Regeneration")]
68  protected float m_fRegenScale;
69 
70  [Attribute("0.3", UIWidgets.Auto, "Resilience regeneration scale while unconscious\n[x * 100%]")]
71  protected float m_fUnconsciousRegenerationScale;
72 
73  [Attribute(defvalue: "true", uiwidget: UIWidgets.CheckBox, desc: "Whether unconsciousness is allowed", category: "Unconsciousness")]
74  protected bool m_bPermitUnconsciousness;
75 
76  [Attribute(defvalue: "0.05", uiwidget: UIWidgets.Slider, desc: "Affects how much the bleeding is reduced", params: "0 1 0.001", precision: 3, category: "Tourniquets")]
77  protected float m_fTourniquetStrengthMultiplier;
78 
79  //-----------------------------------------------------------------------------------------------------------
80  event override void OnInit(IEntity owner)
81  {
82  SCR_BaseGameMode baseGameMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode());
83  if (baseGameMode)
84  s_HealthSettings = baseGameMode.GetGameModeHealthSettings();
85 
86  LIMB_GROUPS = {};
87  SCR_Enum.GetEnumValues(ECharacterHitZoneGroup, LIMB_GROUPS);
88 
89 #ifdef ENABLE_DIAG
90  DiagInit(owner);
91 #endif
92  }
93 
94  //-----------------------------------------------------------------------------------------------------------
96  protected bool ShouldBeUnconscious()
97  {
98  HitZone bloodHZ = GetBloodHitZone();
99  if (!bloodHZ)
100  return false;
101 
102  ECharacterBloodState bloodState = bloodHZ.GetDamageState();
103  if (bloodHZ.GetDamageStateThreshold(bloodState) <= bloodHZ.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS))
104  return true;
105 
106  HitZone resilienceHZ = GetResilienceHitZone();
107  if (!resilienceHZ)
108  return false;
109 
110 
111  ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
112  if (!character)
113  return false;
114 
115  CharacterControllerComponent controller = character.GetCharacterController();
116  if (!controller)
117  return false;
118 
119  ECharacterResilienceState resilienceState = resilienceHZ.GetDamageState();
120 
121  if (controller.IsUnconscious())
122  {
123  if (resilienceHZ.GetDamageStateThreshold(resilienceState) <= resilienceHZ.GetDamageStateThreshold(ECharacterResilienceState.WEAKENED))
124  return true;
125  }
126  else
127  {
128  if (resilienceHZ.GetDamageStateThreshold(resilienceState) <= resilienceHZ.GetDamageStateThreshold(ECharacterResilienceState.UNCONSCIOUS))
129  return true;
130  }
131 
132  return false;
133  }
134 
135  //-----------------------------------------------------------------------------------------------------------
136  void UpdateConsciousness()
137  {
138  bool unconscious = ShouldBeUnconscious();
139 
140  // If unconsciousness is not allowed, kill character
141  // Also kill the character if the blood state is not high enough for being unconsciousness
142  if (unconscious && (!GetPermitUnconsciousness() || (m_pBloodHitZone && m_pBloodHitZone.GetDamageState() == ECharacterBloodState.DESTROYED)))
143  {
144  Kill(GetInstigator());
145  return;
146  }
147 
148  ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
149  if (!character)
150  return;
151 
152  CharacterControllerComponent controller = character.GetCharacterController();
153  if (!controller)
154  return;
155 
156  controller.SetUnconscious(unconscious);
157  }
158 
159  //------------------------------------------------------------------------------------------------
161  void ForceUnconsciousness(float resilienceHealth = 0)
162  {
163  if (!GetPermitUnconsciousness())
164  return;
165 
166  HitZone resilienceHZ = GetResilienceHitZone();
167  if (!resilienceHZ)
168  return;
169 
170  resilienceHZ.SetHealth(resilienceHealth);
171  UpdateConsciousness();
172  }
173 
174  //-----------------------------------------------------------------------------------------------------------
176  void InsertArmorData(notnull SCR_CharacterHitZone charHitZone, SCR_ArmoredClothItemData attributes)
177  {
178  if (!m_mClothItemDataMap)
179  m_mClothItemDataMap = new map<SCR_CharacterHitZone, ref SCR_ArmoredClothItemData>();
180 
181  m_mClothItemDataMap.Insert(charHitZone, attributes);
182  }
183 
184  //-----------------------------------------------------------------------------------------------------------
186  void RemoveArmorData(notnull SCR_CharacterHitZone charHitZone)
187  {
188  if (!m_mClothItemDataMap)
189  return;
190 
191  if (m_mClothItemDataMap.Contains(charHitZone))
192  m_mClothItemDataMap.Remove(charHitZone);
193 
194  if (m_mClothItemDataMap.IsEmpty())
195  m_mClothItemDataMap = null;
196  }
197 
198  //-----------------------------------------------------------------------------------------------------------
200  void UpdateArmorDataMap(notnull SCR_ArmoredClothItemData armorAttr, bool remove)
201  {
202  foreach (string hitZoneName : armorAttr.m_aProtectedHitZones)
203  {
204  SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(GetHitZoneByName(hitZoneName));
205  if (hitZone)
206  {
207  if (remove)
208  {
209  RemoveArmorData(hitZone);
210  continue;
211  }
212 
213  SCR_ArmoredClothItemData armoredClothItemData;
214  armoredClothItemData = new SCR_ArmoredClothItemData(
215  armorAttr.m_eProtection,
216  armorAttr.m_aProtectedHitZones,
217  armorAttr.m_aProtectedDamageTypes,
218  armorAttr.m_MaterialResourceName
219  );
220 
221  InsertArmorData(hitZone, armoredClothItemData);
222  }
223  }
224  }
225 
226  //-----------------------------------------------------------------------------------------------------------
227  override GameMaterial OverrideHitMaterial(HitZone struckHitzone)
228  {
229  SCR_CharacterHitZone charHitZone = SCR_CharacterHitZone.Cast(struckHitzone);
230  if (!charHitZone)
231  return null;
232 
233  SCR_ArmoredClothItemData armorData = GetArmorData(charHitZone);
234  if (!armorData)
235  return null;
236 
237  if (!armorData.m_Material)
238  return null;
239 
240  return armorData.m_Material.GetResource().ToBaseContainer();
241  }
242 
243  //-----------------------------------------------------------------------------------------------------------
245  SCR_ArmoredClothItemData GetArmorData(notnull SCR_CharacterHitZone charHitZone)
246  {
247  if (!m_mClothItemDataMap)
248  return null;
249 
250  if (m_mClothItemDataMap.Contains(charHitZone))
251  return m_mClothItemDataMap.Get(charHitZone);
252 
253  return null;
254  }
255 
256  //-----------------------------------------------------------------------------------------------------------
258  float GetArmorProtection(notnull SCR_CharacterHitZone charHitZone, EDamageType damageType)
259  {
260  SCR_ArmoredClothItemData localArmorData = GetArmorData(charHitZone);
261  if (!localArmorData)
262  return 0;
263 
264  foreach(EDamageType protectedType : localArmorData.m_aProtectedDamageTypes)
265  {
266  if (damageType == protectedType)
267  return localArmorData.m_eProtection;
268  }
269 
270  return 0;
271  }
272 
273  //-----------------------------------------------------------------------------------------------------------
275  void ArmorHitEventEffects(float damage)
276  {
277  SoundHit(true, EDamageType.KINETIC);
278 
279  ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
280  if (!character)
281  return;
282 
283  CharacterControllerComponent controller = character.GetCharacterController();
284  if (!controller)
285  return;
286 
287  CharacterInputContext context = controller.GetInputContext();
288 
289  if (context)
290  context.SetHit(EHitReactionType.HIT_REACTION_LIGHT, 0);
291  }
292 
293  //-----------------------------------------------------------------------------------------------------------
295  void ArmorHitEventDamage(EDamageType type, float damage, IEntity instigator)
296  {
297  if (m_pResilienceHitZone)
298  m_pResilienceHitZone.HandleDamage(damage, type, instigator);
299  }
300 
301  //-----------------------------------------------------------------------------------------------------------
303  override void OnDamageOverTimeAdded(EDamageType dType, float dps, HitZone hz)
304  {
305  super.OnDamageOverTimeAdded(dType, dps, hz);
306 
307  if (hz.IsProxy())
308  return;
309 
310  // Play heal sound and cancel regeneration when bleeding starts
311  if (dType == EDamageType.HEALING)
312  {
313  SoundHeal();
314  return;
315  }
316 
317  // Cancel regeneration when bleeding starts
318  if (dType == EDamageType.REGENERATION)
319  return;
320 
322  if (regenHZ)
323  regenHZ.RemovePassiveRegeneration();
324  }
325 
326  //-----------------------------------------------------------------------------------------------------------
328  override void OnDamageOverTimeRemoved(EDamageType dType, HitZone hz)
329  {
330  super.OnDamageOverTimeRemoved(dType, hz);
331 
332  if (hz.IsProxy())
333  return;
334 
335  // Schedule regeneration when bleeding stops
336  if (dType == EDamageType.REGENERATION)
337  return;
338 
340  if (regenHZ)
341  regenHZ.ScheduleRegeneration();
342  }
343 
344  //-----------------------------------------------------------------------------------------------------------
346  override void OnDamageStateChanged(EDamageState state)
347  {
348  super.OnDamageStateChanged(state);
349 
350  if (state == EDamageState.DESTROYED)
351  {
352  if (IsRplReady() || !GetDefaultHitZone().IsProxy())
353  SoundDeath();
354 
355  RemoveAllBleedingParticlesAfterDeath();
356  }
357  }
358 
359  //------------------------------------------------------------------------------------------------
360  void SoundHit(bool critical, EDamageType damageType)
361  {
362  // Ignore if knocked out or dead
363  if (LocalGetLifeState() != ECharacterLifeState.ALIVE)
364  return;
365 
366  if (m_CommunicationSound)
367  m_CommunicationSound.SoundEventHit(critical, damageType);
368  }
369 
370  //------------------------------------------------------------------------------------------------
372  void SoundKnockout()
373  {
374  if (m_CommunicationSound && m_CommunicationSound.IsPlaying())
375  m_CommunicationSound.SoundEventDeath(true);
376  }
377 
378  //------------------------------------------------------------------------------------------------
380  void SoundDeath()
381  {
382  bool silent;
383 
384  // Silent if killed with headshot
385  if (m_pHeadHitZone && m_pHeadHitZone.GetDamageState() == EDamageState.DESTROYED)
386  silent = true;
387 
388  // Silent if already knocked out
389  if (LocalGetLifeState() == ECharacterLifeState.INCAPACITATED)
390  silent = true;
391 
392  if (m_CommunicationSound)
393  m_CommunicationSound.SoundEventDeath(silent);
394  }
395 
396  //------------------------------------------------------------------------------------------------
397  void SoundHeal()
398  {
399  if (m_CommunicationSound && LocalGetLifeState() == ECharacterLifeState.ALIVE)
400  m_CommunicationSound.DelayedSoundEventPriority(SCR_SoundEvent.SOUND_VOICE_PAIN_RELIEVE, SCR_ECommunicationSoundEventPriority.SOUND_PAIN_RELIEVE, SCR_CommunicationSoundComponent.DEFAULT_EVENT_PRIORITY_DELAY);
401  }
402 
403  //-----------------------------------------------------------------------------------------------------------
405  void RemoveAllBleedingParticlesAfterDeath()
406  {
407  float bleedingRate;
408  if (m_pBloodHitZone)
409  bleedingRate = DEATH_BLEEDOUT_SCALE * m_pBloodHitZone.GetDamageOverTime(EDamageType.BLEEDING);
410 
411  float delay;
412  if (bleedingRate > 0)
413  delay = m_pBloodHitZone.GetHealth() / bleedingRate;
414 
415  // Damage over time is not simulated after character death
416  GetGame().GetCallqueue().Remove(RemoveAllBleedingParticles);
417  GetGame().GetCallqueue().CallLater(RemoveAllBleedingParticles, delay * 1000);
418  }
419 
420  //-----------------------------------------------------------------------------------------------------------
421  override void FullHeal(bool ignoreHealingDOT = true)
422  {
423  RemoveAllBleedings();
424  super.FullHeal(ignoreHealingDOT);
425  }
426 
427  //-----------------------------------------------------------------------------------------------------------
428  void UpdateBloodClothes()
429  {
430  if (GetState() == EDamageState.DESTROYED)
431  {
432  GetGame().GetCallqueue().Remove(UpdateBloodClothes);
433  return;
434  }
435 
436  int bleedingHitZonesCount;
437  if (m_aBleedingHitZones)
438  bleedingHitZonesCount = m_aBleedingHitZones.Count();
439 
440  if (bleedingHitZonesCount == 0)
441  {
442  GetGame().GetCallqueue().Remove(UpdateBloodClothes);
443  return;
444  }
445 
446  SCR_CharacterHitZone characterHitZone;
447  for (int i; i < bleedingHitZonesCount; i++)
448  {
449  characterHitZone = SCR_CharacterHitZone.Cast(m_aBleedingHitZones[i]);
450  if (characterHitZone)
451  characterHitZone.AddBloodToClothes();
452  }
453  }
454 
455  //-----------------------------------------------------------------------------------------------------------
456  void SetBloodHitZone(HitZone hitZone)
457  {
458  m_pBloodHitZone = SCR_CharacterBloodHitZone.Cast(hitZone);
459  }
460 
461  //-----------------------------------------------------------------------------------------------------------
462  HitZone GetBloodHitZone()
463  {
464  return m_pBloodHitZone;
465  }
466 
467  //------------------------------------------------------------------------------------------------
468  // Scale for effects that become worsened with blood level
469  float GetResilienceRegenScale()
470  {
471  if (!m_pBloodHitZone)
472  return 1;
473 
474  float criticalBloodLevel = m_pBloodHitZone.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS);
475  if (criticalBloodLevel >= 1)
476  return 0;
477 
478  float currentBloodLevel = m_pBloodHitZone.GetDamageStateThreshold(m_pBloodHitZone.GetDamageState());
479  if (currentBloodLevel < criticalBloodLevel)
480  return 0;
481 
482  float resilienceRegenScale = (currentBloodLevel - criticalBloodLevel) / (1 - criticalBloodLevel);
483 
484  if (IsDamagedOverTime(EDamageType.BLEEDING) && LocalGetLifeState() == ECharacterLifeState.INCAPACITATED)
485  resilienceRegenScale *= m_fUnconsciousRegenerationScale;
486 
487  return resilienceRegenScale;
488  }
489 
490  //-----------------------------------------------------------------------------------------------------------
491  void GetBleedingHitZones(out notnull array<HitZone> hitZones)
492  {
493  if (m_aBleedingHitZones)
494  hitZones.Copy(m_aBleedingHitZones);
495  }
496 
497  //-----------------------------------------------------------------------------------------------------------
498  void SetDOTScale(float rate, bool changed)
499  {
500  m_fDOTScale = rate;
501  m_bDOTScaleChangedByGM = changed;
502  }
503 
504  //------------------------------------------------------------------------------------------------
505  float GetDOTScale()
506  {
507  if (!s_HealthSettings || m_bDOTScaleChangedByGM)
508  return m_fDOTScale;
509 
510  return s_HealthSettings.GetBleedingScale();
511  }
512 
513  //-----------------------------------------------------------------------------------------------------------
514  void SetRegenScale(float rate, bool changed)
515  {
516  m_fRegenScale = rate;
517  m_bRegenScaleChangedByGM = changed;
518  }
519 
520  //------------------------------------------------------------------------------------------------
521  // Return base regen scale
522  float GetRegenScale()
523  {
524  if (!s_HealthSettings || m_bRegenScaleChangedByGM)
525  return m_fRegenScale;
526 
527  return s_HealthSettings.GetRegenScale();
528  }
529 
530  //-----------------------------------------------------------------------------------------------------------
532  bool GetPermitUnconsciousness()
533  {
535  return false;
536 
537  if (!s_HealthSettings || m_bUnconsciousnessSettingsChangedByGM)
539 
540  return s_HealthSettings.IsUnconsciousnessPermitted();
541  }
542 
543  //-----------------------------------------------------------------------------------------------------------
544  void SetPermitUnconsciousness(bool permit, bool changed)
545  {
546  m_bPermitUnconsciousness = permit;
547  m_bUnconsciousnessSettingsChangedByGM = changed;
548  }
549 
550  //-----------------------------------------------------------------------------------------------------------
551  protected ECharacterLifeState LocalGetLifeState()
552  {
553  ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
554  if (!character)
555  return ECharacterLifeState.ALIVE;
556 
557  CharacterControllerComponent controller = character.GetCharacterController();
558  if (!controller)
559  return ECharacterLifeState.ALIVE;
560 
561  return controller.GetLifeState();
562  }
563 
564  //-----------------------------------------------------------------------------------------------------------
565  void SetOverrideCharacterMedical(bool permit)
566  {
567  m_bOverrideCharacterMedical = permit;
568  }
569 
570  //-----------------------------------------------------------------------------------------------------------
571  bool GetOverrideCharacterMedical()
572  {
573  return m_bOverrideCharacterMedical;
574  }
575 
576  //-----------------------------------------------------------------------------------------------------------
577  void SetResilienceHitZone(HitZone hitZone)
578  {
579  m_pResilienceHitZone = SCR_CharacterResilienceHitZone.Cast(hitZone);
580  }
581 
582  //-----------------------------------------------------------------------------------------------------------
583  HitZone GetResilienceHitZone()
584  {
585  return m_pResilienceHitZone;
586  }
587 
588  //-----------------------------------------------------------------------------------------------------------
589  void SetHeadHitZone(HitZone hitZone)
590  {
591  m_pHeadHitZone = SCR_CharacterHeadHitZone.Cast(hitZone);
592  }
593 
594  //-----------------------------------------------------------------------------------------------------------
595  HitZone GetHeadHitZone()
596  {
597  return m_pHeadHitZone;
598  }
599 
600  //------------------------------------------------------------------------------------------------
606  [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
607  void RpcDo_AddBloodToClothes(int hitZoneIndex, float immediateBloodEffect)
608  {
609  array<HitZone> hitZones = {};
610  GetAllHitZones(hitZones);
611  SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(hitZones.Get(hitZoneIndex));
612  if (hitZone)
613  hitZone.AddBloodToClothes(immediateBloodEffect);
614  }
615 
616  //-----------------------------------------------------------------------------------------------------------
622  void AddBleedingHitZone(notnull SCR_CharacterHitZone hitZone, int colliderDescriptorIndex = -1)
623  {
624  if (!m_aBleedingHitZones)
625  m_aBleedingHitZones = {};
626 
627  if (!m_aBleedingHitZones.Contains(hitZone))
628  m_aBleedingHitZones.Insert(hitZone);
629 
630  // In case bleeding is started outside of normal context, full health will prevent DOT. This block will circumvent this issue
631  float hitZoneDamageMultiplier = hitZone.GetHealthScaled();
632  float bleedingRate = hitZone.GetMaxBleedingRate() - hitZone.GetMaxBleedingRate() * hitZoneDamageMultiplier;
633 
634  GetGame().GetCallqueue().CallLater(UpdateBloodClothes, BLOOD_CLOTHES_UPDATE_PERIOD, true);
635 
636  if (colliderDescriptorIndex == -1)
637  colliderDescriptorIndex = Math.RandomInt(0, hitZone.GetNumColliderDescriptors() - 1);
638 
639  CreateBleedingParticleEffect(hitZone, bleedingRate, colliderDescriptorIndex);
640 
641  if (m_pBloodHitZone)
642  {
643  SCR_BleedingHitZoneParameters localBleedingHZParams = new SCR_BleedingHitZoneParameters(hitZone, bleedingRate);
644  m_pBloodHitZone.AddBleedingHZToMap(hitZone, localBleedingHZParams);
645  UpdateBleedingHitZones();
646  }
647 
648  // The rest of the code is handled on authority only
649  if (hitZone.IsProxy())
650  return;
651 
652  array<HitZone> hitZones = {};
653  GetAllHitZones(hitZones);
654 
655  int hitZoneIndex = hitZones.Find(hitZone);
656  if (hitZoneIndex >= 0)
657  Rpc(RpcDo_AddBleedingHitZone, hitZoneIndex, colliderDescriptorIndex, GetGroupBleedingRate(hitZone.GetHitZoneGroup()));
658  }
659 
660  //-----------------------------------------------------------------------------------------------------------
664  void RemoveBleedingHitZone(notnull HitZone hitZone)
665  {
666  RemoveBleedingParticleEffect(hitZone);
667  if (m_aBleedingHitZones)
668  {
669  m_aBleedingHitZones.RemoveItem(hitZone);
670  if (m_aBleedingHitZones.IsEmpty())
671  m_aBleedingHitZones = null;
672  }
673 
674  SCR_CharacterHitZone characterHitZone = SCR_CharacterHitZone.Cast(hitZone);
675  if (characterHitZone && m_pBloodHitZone)
676  m_pBloodHitZone.RemoveBleedingHZFromMap(characterHitZone);
677 
678  if (hitZone.IsProxy())
679  return;
680 
681  hitZone.SetDamageOverTime(EDamageType.BLEEDING, 0);
682  UpdateBleedingHitZones();
683 
684  array<HitZone> hitZones = {};
685  GetAllHitZones(hitZones);
686  int hitZoneIndex = hitZones.Find(hitZone);
687  if (hitZoneIndex >= 0 && !GetOwner().IsDeleted())
688  Rpc(RpcDo_RemoveBleedingHitZone, hitZoneIndex, GetGroupBleedingRate(characterHitZone.GetHitZoneGroup()));
689  }
690 
691  //------------------------------------------------------------------------------------------------
692  void UpdateBleedingHitZones()
693  {
694  SCR_CharacterHitZone hitZone;
695  float bleedingDamage;
696  float totalBleedingDamage;
697 
698  if (!m_pBloodHitZone.GetHitZoneDOTMap())
699  {
700  m_pBloodHitZone.SetDamageOverTime(EDamageType.BLEEDING, 0);
701  m_aGroupBleedingRates = null;
702  return;
703  }
704 
705  m_aGroupBleedingRates = {};
706  foreach(int enumValue : LIMB_GROUPS)
707  {
708  m_aGroupBleedingRates.Insert(0);
709  }
710 
711  foreach (SCR_BleedingHitZoneParameters localBleedingHZParams : m_pBloodHitZone.GetHitZoneDOTMap())
712  {
713  if (!localBleedingHZParams)
714  continue;
715 
716  hitZone = SCR_CharacterHitZone.Cast(localBleedingHZParams.m_hHitZone);
717  ECharacterHitZoneGroup hitZoneGroup = hitZone.GetHitZoneGroup();
718 
719  if (localBleedingHZParams.m_fDamageOverTime != 0)
720  hitZone.SetDamageOverTime(EDamageType.BLEEDING, 1e-5);
721 
722  if (!GetGroupTourniquetted(hitZoneGroup))
723  bleedingDamage = localBleedingHZParams.m_fDamageOverTime;
724  else
725  bleedingDamage = localBleedingHZParams.m_fDamageOverTime * m_fTourniquetStrengthMultiplier;
726 
727  int hitZoneGroupIndex = LIMB_GROUPS.Find(hitZoneGroup);
728  m_aGroupBleedingRates[hitZoneGroupIndex] = m_aGroupBleedingRates[hitZoneGroupIndex] + localBleedingHZParams.m_fDamageOverTime;
729 
730  totalBleedingDamage += bleedingDamage;
731  }
732 
733  m_pBloodHitZone.SetDamageOverTime(EDamageType.BLEEDING, totalBleedingDamage);
734  }
735 
736  //------------------------------------------------------------------------------------------------
743  [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
744  void RpcDo_AddBleedingHitZone(int hitZoneIndex, int colliderDescriptorIndex, float groupBleedingRate)
745  {
746  array<HitZone> hitZones = {};
747  GetAllHitZones(hitZones);
748  SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(hitZones.Get(hitZoneIndex));
749  if (!hitZone)
750  return;
751 
752  AddBleedingHitZone(hitZone, colliderDescriptorIndex);
753 
754  if (groupBleedingRate >= 0)
755  SetGroupBleedingRate(hitZone.GetHitZoneGroup(), groupBleedingRate);
756  }
757 
758  //------------------------------------------------------------------------------------------------
763  [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
764  void RpcDo_RemoveBleedingHitZone(int hitZoneIndex, float groupBleedingRate)
765  {
766  array<HitZone> hitZones = {};
767  GetAllHitZones(hitZones);
768  SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(hitZones.Get(hitZoneIndex));
769  if (!hitZone)
770  return;
771 
772  RemoveBleedingHitZone(hitZone);
773 
774  if (groupBleedingRate >= 0)
775  SetGroupBleedingRate(hitZone.GetHitZoneGroup(), groupBleedingRate);
776  }
777 
778  //-----------------------------------------------------------------------------------------------------------
780  void CreateBleedingParticleEffect(notnull HitZone hitZone, float bleedingRate, int colliderDescriptorIndex)
781  {
782  if (System.IsConsoleApp())
783  return;
784 
785  // Play Bleeding particle
786  if (m_sBleedingParticle.IsEmpty())
787  return;
788 
789  RemoveBleedingParticleEffect(hitZone);
790 
791  if (bleedingRate == 0 || m_fBleedingParticleRateScale == 0)
792  return;
793 
794  // TODO: Blood traces on ground that should be left regardless of clothing, perhaps just delayed
795  SCR_CharacterHitZone characterHitZone = SCR_CharacterHitZone.Cast(hitZone);
796  if (characterHitZone.IsCovered())
797  return;
798 
799  // Get bone node
800  vector transform[4];
801  int boneIndex;
802  int boneNode;
803  if (!hitZone.TryGetColliderDescription(GetOwner(), colliderDescriptorIndex, transform, boneIndex, boneNode))
804  return;
805 
806  // Create particle emitter
807  ParticleEffectEntitySpawnParams spawnParams();
808  spawnParams.Parent = GetOwner();
809  spawnParams.PivotID = boneNode;
810  ParticleEffectEntity particleEmitter = ParticleEffectEntity.SpawnParticleEffect(m_sBleedingParticle, spawnParams);
811  if (System.IsConsoleApp())
812  return;
813 
814  if (!particleEmitter)
815  {
816  Print("Particle emitter: " + particleEmitter.ToString() + " There was a problem with creating the particle emitter: " + m_sBleedingParticle, LogLevel.WARNING);
817  return;
818  }
819 
820  // Track particle emitter in array
821  if (!m_mBleedingParticles)
822  m_mBleedingParticles = new map<HitZone, ParticleEffectEntity>;
823 
824  m_mBleedingParticles.Insert(hitZone, particleEmitter);
825 
826  // Play particles
827  Particles particles = particleEmitter.GetParticles();
828  if (particles)
829  particles.MultParam(-1, EmitterParam.BIRTH_RATE, bleedingRate * m_fBleedingParticleRateScale);
830  else
831  Print("Particle: " + particles.ToString() + " Bleeding particle likely not created properly: " + m_sBleedingParticle, LogLevel.WARNING);
832  }
833 
834  //-----------------------------------------------------------------------------------------------------------
838  void RemoveBleedingParticleEffect(HitZone hitZone)
839  {
840  if (!m_mBleedingParticles)
841  return;
842 
843  ParticleEffectEntity particleEmitter = m_mBleedingParticles.Get(hitZone);
844  if (particleEmitter)
845  {
846  particleEmitter.StopEmission();
847  m_mBleedingParticles.Remove(hitZone);
848  }
849 
850  if (m_mBleedingParticles.IsEmpty())
851  m_mBleedingParticles = null;
852  }
853 
854  //-----------------------------------------------------------------------------------------------------------
856  void RemoveAllBleedingParticles()
857  {
858  if (!m_aBleedingHitZones || m_aBleedingHitZones.IsEmpty())
859  return;
860 
861  foreach (HitZone hitZone : m_aBleedingHitZones)
862  RemoveBleedingParticleEffect(hitZone);
863  }
864 
865  //-----------------------------------------------------------------------------------------------------------
867  void RemoveAllBleedings()
868  {
869  if (m_aBleedingHitZones)
870  {
871  HitZone hitZone;
872  for (int i = m_aBleedingHitZones.Count() - 1; i >= 0; i--)
873  {
874  hitZone = m_aBleedingHitZones.Get(i);
875  if (hitZone)
876  RemoveBleedingHitZone(hitZone);
877  }
878  }
879 
880  if (m_pBloodHitZone)
881  m_pBloodHitZone.SetDamageOverTime(EDamageType.BLEEDING, 0);
882  }
883 
884  //-----------------------------------------------------------------------------------------------------------
886  void RemoveGroupBleeding(ECharacterHitZoneGroup charHZGroup)
887  {
888  if (!m_aBleedingHitZones)
889  return;
890 
891  if (charHZGroup == ECharacterHitZoneGroup.VIRTUAL)
892  return;
893 
894  array<HitZone> aBleedingHitZones = {};
895  aBleedingHitZones.Copy(m_aBleedingHitZones);
896  SCR_CharacterHitZone charHitZone;
897  foreach (HitZone hitZone : aBleedingHitZones)
898  {
899  charHitZone = SCR_CharacterHitZone.Cast(hitZone);
900  if (!charHitZone || charHitZone.GetHitZoneGroup() != charHZGroup)
901  continue;
902 
903  RemoveBleedingHitZone(hitZone);
904  }
905  }
906 
907  //-----------------------------------------------------------------------------------------------------------
909  void AddParticularBleeding(string hitZoneName = "Chest", ECharacterDamageState intensityEnum = ECharacterDamageState.WOUNDED, float intensityFloat = -1)
910  {
911  SCR_CharacterHitZone targetHitZone = SCR_CharacterHitZone.Cast( GetHitZoneByName(hitZoneName));
912 
913  if (!targetHitZone || targetHitZone.IsProxy())
914  return;
915 
916  // If someone wants to add bleeding to undamaged hitZone, return
917  if (intensityEnum == ECharacterDamageState.UNDAMAGED)
918  return;
919 
920  // if intensityFloat is unset use the enum instead
921  if (intensityFloat < 0)
922  targetHitZone.SetHealthScaled(targetHitZone.GetDamageStateThreshold(ECharacterDamageState.WOUNDED));
923  else
924  targetHitZone.SetHealthScaled(intensityFloat);
925 
926  AddBleedingHitZone(targetHitZone);
927  }
928 
929  //-----------------------------------------------------------------------------------------------------------
931  void AddRandomBleeding()
932  {
933  // TODO@FAC figure out why AI don't heal themselves after this function is called on them
934  array<HitZone> hitZones = {};
935  array<HitZone> validHitZones = {};
936  GetPhysicalHitZones(hitZones);
937 
938  foreach (HitZone hitZone: hitZones)
939  {
940  // Is not already bleeding
941  if (hitZone.GetDamageOverTime(EDamageType.BLEEDING) > 0)
942  continue;
943 
944  //Is character hz
945  SCR_CharacterHitZone characterHitZone = SCR_CharacterHitZone.Cast(hitZone);
946  if (characterHitZone)
947  validHitZones.Insert(characterHitZone);
948  }
949 
950  if (validHitZones.IsEmpty())
951  return;
952 
953 
954  SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(validHitZones.GetRandomElement());
955  AddBleedingHitZone(hitZone);
956  }
957 
958  //-----------------------------------------------------------------------------------------------------------
959  ECharacterHitZoneGroup FindAssociatedHitZoneGroup(EBandagingAnimationBodyParts bodyPartToBandage)
960  {
961  array<HitZone> hitZones = {};
962 
963  GetPhysicalHitZones(hitZones);
964  SCR_CharacterHitZone charHitZone;
965 
966  foreach (HitZone hitZone : hitZones)
967  {
968  charHitZone = SCR_CharacterHitZone.Cast(hitZone);
969  if (charHitZone && charHitZone.GetBodyPartToHeal() == bodyPartToBandage)
970  return charHitZone.GetHitZoneGroup();
971  }
972 
973  return null;
974  }
975 
976  //-----------------------------------------------------------------------------------------------------------
977  EBandagingAnimationBodyParts FindAssociatedBandagingBodyPart(ECharacterHitZoneGroup hitZoneGroup)
978  {
979  array<HitZone> hitZones = {};
980 
981  GetPhysicalHitZones(hitZones);
982  SCR_CharacterHitZone charHitZone;
983 
984  foreach (HitZone hitZone : hitZones)
985  {
986  charHitZone = SCR_CharacterHitZone.Cast(hitZone);
987  if (charHitZone && charHitZone.GetHitZoneGroup() == hitZoneGroup)
988  return charHitZone.GetBodyPartToHeal();
989  }
990 
991  return null;
992  }
993 
994  //-----------------------------------------------------------------------------------------------------------
995  void GetBandageAnimHitzones(EBandagingAnimationBodyParts eBandagingAnimBodyParts, out notnull array<HitZone> GroupHitZones)
996  {
997  array<HitZone> allGroupedHitZones = {};
998  GetAllHitZones(allGroupedHitZones);
999  SCR_CharacterHitZone charHitZone;
1000 
1001  foreach (HitZone hitZone : allGroupedHitZones)
1002  {
1003  charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1004  if (charHitZone && charHitZone.GetBodyPartToHeal() == eBandagingAnimBodyParts)
1005  GroupHitZones.Insert(hitZone);
1006  }
1007  }
1008 
1009  //-----------------------------------------------------------------------------------------------------------
1010  float GetGroupHealthScaled(ECharacterHitZoneGroup hitZoneGroup)
1011  {
1012  float totalGroupHealth;
1013  float hitZoneQuantity;
1014 
1015  array<HitZone> allGroupedHitZones = {};
1016  GetAllHitZones(allGroupedHitZones);
1017  SCR_CharacterHitZone charHitZone;
1018 
1019  foreach (HitZone hitZone : allGroupedHitZones)
1020  {
1021  charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1022  if (!charHitZone || charHitZone.GetHitZoneGroup() != hitZoneGroup)
1023  continue;
1024 
1025  totalGroupHealth += charHitZone.GetHealthScaled();
1026  hitZoneQuantity ++;
1027  }
1028 
1029  if (hitZoneQuantity <= 0)
1030  return 0;
1031 
1032  return totalGroupHealth / hitZoneQuantity;
1033  }
1034 
1035  //------------------------------------------------------------------------------------------------
1036  // Get group bleeding rate for specific hitZone group
1037  private float GetGroupBleedingRate(ECharacterHitZoneGroup group)
1038  {
1039  if (!m_aGroupBleedingRates)
1040  return 0;
1041 
1042  if (group < 0 && group >= m_aGroupBleedingRates.Count())
1043  return 0;
1044 
1045  return m_aGroupBleedingRates[LIMB_GROUPS.Find(group)];
1046  }
1047 
1048  //------------------------------------------------------------------------------------------------
1049  // Set bleeding rate
1056  private void SetGroupBleedingRate(ECharacterHitZoneGroup group, float rate)
1057  {
1058  if (!m_aGroupBleedingRates && rate > 0)
1059  {
1060  m_aGroupBleedingRates = {};
1061  foreach(int enumValue : LIMB_GROUPS)
1062  {
1063  m_aGroupBleedingRates.Insert(0);
1064  }
1065  }
1066 
1067  //If trying to set bleeding rate to current bleeding rate, fail
1068  if (!m_aGroupBleedingRates || m_aGroupBleedingRates[LIMB_GROUPS.Find(group)] == rate)
1069  return;
1070 
1071  m_aGroupBleedingRates[LIMB_GROUPS.Find(group)] = rate;
1072 
1073  foreach (float bleedingRate: m_aGroupBleedingRates)
1074  {
1075  if (bleedingRate > 0)
1076  return;
1077  }
1078 
1079  //If having failed to set any bleedingrates, set array back to null
1080  m_aGroupBleedingRates = null;
1081  }
1082 
1083  //-----------------------------------------------------------------------------------------------------------
1084  override float GetGroupDamageOverTime(ECharacterHitZoneGroup hitZoneGroup, EDamageType damageType)
1085  {
1086  if (damageType == EDamageType.BLEEDING)
1087  return GetGroupBleedingRate(hitZoneGroup);
1088 
1089  return super.GetGroupDamageOverTime(hitZoneGroup, damageType);
1090  }
1091 
1092  //-----------------------------------------------------------------------------------------------------------
1093  bool GetGroupTourniquetted(ECharacterHitZoneGroup hitZoneGroup)
1094  {
1095  return m_aTourniquettedGroups && m_aTourniquettedGroups.Contains(hitZoneGroup);
1096  }
1097 
1098  //------------------------------------------------------------------------------------------------
1099  void SetTourniquettedGroup(ECharacterHitZoneGroup hitZoneGroup, bool setTourniquetted)
1100  {
1101  if (setTourniquetted)
1102  {
1103  if (!m_aTourniquettedGroups)
1104  m_aTourniquettedGroups = {};
1105  else if (m_aTourniquettedGroups.Contains(hitZoneGroup))
1106  return;
1107 
1108  m_aTourniquettedGroups.Insert(hitZoneGroup);
1109  }
1110  else
1111  {
1112  if (!m_aTourniquettedGroups || !m_aTourniquettedGroups.Contains(hitZoneGroup))
1113  return;
1114  else
1115  m_aTourniquettedGroups.RemoveItem(hitZoneGroup);
1116 
1117  if (m_aTourniquettedGroups.IsEmpty())
1118  m_aTourniquettedGroups = null;
1119  }
1120 
1121  UpdateBleedingHitZones();
1122  UpdateCharacterGroupDamage(hitZoneGroup);
1123  }
1124 
1125  //-----------------------------------------------------------------------------------------------------------
1126  bool GetGroupIsBeingHealed(ECharacterHitZoneGroup hitZoneGroup)
1127  {
1128  return m_aBeingHealedGroup && m_aBeingHealedGroup.Contains(hitZoneGroup);
1129  }
1130 
1131  //------------------------------------------------------------------------------------------------
1132  void SetGroupIsBeingHealed(ECharacterHitZoneGroup hitZoneGroup, bool setIsBeingHealed)
1133  {
1134  if (setIsBeingHealed)
1135  {
1136  if (!m_aBeingHealedGroup)
1137  m_aBeingHealedGroup = {};
1138  else if (m_aBeingHealedGroup.Contains(hitZoneGroup))
1139  return;
1140 
1141  m_aBeingHealedGroup.Insert(hitZoneGroup);
1142  }
1143  else
1144  {
1145  if (!m_aBeingHealedGroup || !m_aBeingHealedGroup.Contains(hitZoneGroup))
1146  return;
1147  else
1148  m_aBeingHealedGroup.RemoveItem(hitZoneGroup);
1149 
1150  if (m_aBeingHealedGroup.IsEmpty())
1151  m_aBeingHealedGroup = null;
1152  }
1153  }
1154 
1155  //------------------------------------------------------------------------------------------------
1156  bool GetGroupSalineBagged(ECharacterHitZoneGroup hitZoneGroup)
1157  {
1158  return m_aSalineBaggedGroups && m_aSalineBaggedGroups.Contains(hitZoneGroup);
1159  }
1160 
1161  //------------------------------------------------------------------------------------------------
1164  void SetSalineBaggedGroup(ECharacterHitZoneGroup hitZoneGroup, bool setSalineBagged)
1165  {
1166  if (setSalineBagged)
1167  {
1168  if (!m_aSalineBaggedGroups)
1169  m_aSalineBaggedGroups = {};
1170  else if (m_aSalineBaggedGroups.Contains(hitZoneGroup))
1171  return;
1172 
1173  m_aSalineBaggedGroups.Insert(hitZoneGroup);
1174  }
1175  else
1176  {
1177  if (!m_aSalineBaggedGroups || !m_aSalineBaggedGroups.Contains(hitZoneGroup))
1178  return;
1179  else
1180  m_aSalineBaggedGroups.RemoveItem(hitZoneGroup);
1181 
1182  if (m_aSalineBaggedGroups.IsEmpty())
1183  m_aSalineBaggedGroups = null;
1184  }
1185  }
1186 
1187  //------------------------------------------------------------------------------------------------
1188  HitZone GetMostDOTHitZone(EDamageType damageType, bool includeVirtualHZs = false, array<EHitZoneGroup> allowedGroups = null)
1189  {
1190  float highestDOT;
1191  float localDOT;
1192  HitZone highestDOTHitZone;
1193  array<HitZone> hitZones = {};
1194 
1195  if (includeVirtualHZs)
1196  GetAllHitZones(hitZones);
1197  else
1198  GetPhysicalHitZones(hitZones);
1199 
1200  foreach (HitZone hitZone : hitZones)
1201  {
1202  if (allowedGroups)
1203  {
1204  SCR_CharacterHitZone charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1205  if (!charHitZone)
1206  continue;
1207 
1208  if (!allowedGroups.Contains(charHitZone.GetHitZoneGroup()))
1209  continue;
1210  }
1211 
1212  localDOT = hitZone.GetDamageOverTime(damageType);
1213  if (localDOT > highestDOT)
1214  {
1215  highestDOT = localDOT;
1216  highestDOTHitZone = hitZone;
1217  }
1218  }
1219  return highestDOTHitZone;
1220  }
1221 
1222  //-----------------------------------------------------------------------------------------------------------
1223  static void GetAllLimbs(notnull out array<ECharacterHitZoneGroup> limbs)
1224  {
1225  limbs.Copy(LIMB_GROUPS);
1226  }
1227 
1228  //-----------------------------------------------------------------------------------------------------------
1229  static void GetAllExtremities(notnull out array<ECharacterHitZoneGroup> limbs)
1230  {
1231  limbs.Copy(EXTREMITY_LIMB_GROUPS);
1232  }
1233 
1234  //-----------------------------------------------------------------------------------------------------------
1235  void UpdateCharacterGroupDamage(ECharacterHitZoneGroup hitZoneGroup)
1236  {
1237  float AIMING_DAMAGE_MULTIPLIER = 4;
1238  float TOURNIQUETTED_LEG_DAMAGE = 0.7;
1239  float TOURNIQUETTED_ARMS_DAMAGE = 0.7;
1240  // Updated OnDamageStateChanged, movement damage is equal to the accumulative damage of all hitZones in the limb divided by the amount of limbs of type
1241  if (hitZoneGroup == ECharacterHitZoneGroup.LEFTLEG || hitZoneGroup == ECharacterHitZoneGroup.RIGHTLEG)
1242  {
1243  float leftLegDamage;
1244  if (GetGroupTourniquetted(ECharacterHitZoneGroup.LEFTLEG))
1245  leftLegDamage = TOURNIQUETTED_LEG_DAMAGE;
1246  else
1247  leftLegDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.LEFTLEG));
1248 
1249  float rightLegDamage;
1250  if (GetGroupTourniquetted(ECharacterHitZoneGroup.RIGHTLEG))
1251  rightLegDamage = TOURNIQUETTED_LEG_DAMAGE;
1252  else
1253  rightLegDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.RIGHTLEG));
1254 
1255  float legDamage = (leftLegDamage + rightLegDamage) * 0.5;
1256  SetMovementDamage( legDamage );
1257  }
1258 
1259  if (hitZoneGroup == ECharacterHitZoneGroup.LEFTARM || hitZoneGroup == ECharacterHitZoneGroup.RIGHTARM)
1260  {
1261  float leftArmDamage;
1262  if (GetGroupTourniquetted(ECharacterHitZoneGroup.LEFTARM))
1263  leftArmDamage = TOURNIQUETTED_ARMS_DAMAGE;
1264  else
1265  leftArmDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.LEFTARM));
1266 
1267  float rightArmDamage;
1268  if (GetGroupTourniquetted(ECharacterHitZoneGroup.RIGHTARM))
1269  rightArmDamage = TOURNIQUETTED_ARMS_DAMAGE;
1270  else
1271  rightArmDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.RIGHTARM));
1272 
1273  float armDamage = (leftArmDamage + rightArmDamage) * 0.5;
1274  SetAimingDamage(armDamage * AIMING_DAMAGE_MULTIPLIER);
1275  }
1276  }
1277 
1278  //-----------------------------------------------------------------------------------------------------------
1284  ECharacterHitZoneGroup GetCharMostDOTHitzoneGroup(EDamageType damageType, bool onlyExtremities = false, bool ignoreTQdHitZones = false, bool ignoreIfBeingTreated = false)
1285  {
1286  if (!m_aBleedingHitZones)
1287  return null;
1288 
1289  array<float> DOTValues = {};
1290  typename groupEnum = ECharacterHitZoneGroup;
1291  int groupCount = groupEnum.GetVariableCount();
1292 
1293  for (int i; i < groupCount; i++)
1294  DOTValues.Insert(0);
1295 
1296  float highestDOT;
1297  float localDOT;
1298  array<HitZone> hitZones = {};
1299 
1300  GetPhysicalHitZones(hitZones);
1301  SCR_CharacterHitZone charHitZone;
1302  ECharacterHitZoneGroup group;
1303 
1304  foreach (HitZone hitZone : hitZones)
1305  {
1306  charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1307  if (!charHitZone)
1308  continue;
1309 
1310  // Virtual hitZones don't count to the accumulation of bleedings
1311  group = charHitZone.GetHitZoneGroup();
1312  if (group == EHitZoneGroup.VIRTUAL)
1313  continue;
1314 
1315  // If only extremities are desired for checking, continue if group is not an extremity
1316  if (onlyExtremities && !EXTREMITY_LIMB_GROUPS.Contains(group))
1317  continue;
1318 
1319  float DOT = m_pBloodHitZone.GetPhysicalHZBleedingDOT(charHitZone);
1320  if (DOT == 0)
1321  continue;
1322 
1323  // GetPhysicalHZBleedingDOT() is unaware of tourniquet status, so it is reduced seperately here.
1324  if (GetGroupTourniquetted(group))
1325  {
1327  if (ignoreTQdHitZones)
1328  {
1329  DOTValues[LIMB_GROUPS.Find(group)] = 0;
1330  continue;
1331  }
1332  }
1333 
1334  // if desired, bleedingHitzones that are being treated are skipped so another hitZone will be healed by this inquiry
1335  if (GetGroupIsBeingHealed(group))
1336  {
1337  if (ignoreIfBeingTreated)
1338  {
1339  DOTValues[LIMB_GROUPS.Find(group)] = 0;
1340  continue;
1341  }
1342  }
1343 
1344  DOTValues[LIMB_GROUPS.Find(group)] = DOTValues[LIMB_GROUPS.Find(group)] + DOT;
1345  }
1346 
1347  ECharacterHitZoneGroup mostDOTHitZoneGroup;
1348 
1349  for (int i; i < groupCount; i++)
1350  {
1351  if (DOTValues[i] > highestDOT)
1352  {
1353  highestDOT = DOTValues[i];
1354  mostDOTHitZoneGroup = i;
1355  }
1356  }
1357 
1358  return LIMB_GROUPS.Get(mostDOTHitZoneGroup);
1359  }
1360 
1361  //-----------------------------------------------------------------------------------------------------------
1363  string GetBoneName(ECharacterHitZoneGroup hzGroup)
1364  {
1365  switch (hzGroup)
1366  {
1367  case ECharacterHitZoneGroup.HEAD:
1368  {
1369  return "Head";
1370  } break;
1371 
1372  case ECharacterHitZoneGroup.UPPERTORSO:
1373  {
1374  return "Spine5";
1375  } break;
1376 
1377  case ECharacterHitZoneGroup.LOWERTORSO:
1378  {
1379  return "Spine1";
1380  } break;
1381 
1382  case ECharacterHitZoneGroup.LEFTARM:
1383  {
1384  return "LeftForeArm";
1385  } break;
1386 
1387  case ECharacterHitZoneGroup.RIGHTARM:
1388  {
1389  return "RightForeArm";
1390  } break;
1391 
1392  case ECharacterHitZoneGroup.LEFTLEG:
1393  {
1394  return "LeftKnee";
1395  } break;
1396 
1397  case ECharacterHitZoneGroup.RIGHTLEG:
1398  {
1399  return "RightKnee";
1400  }
1401  }
1402 
1403  return string.Empty;
1404  }
1405 
1406  //------------------------------------------------------------------------------------------------
1407  override void OnPostInit(IEntity owner)
1408  {
1409  super.OnPostInit(owner);
1410  m_CommunicationSound = SCR_CommunicationSoundComponent.Cast(owner.FindComponent(SCR_CommunicationSoundComponent));
1411 
1412  RplComponent rpl = RplComponent.Cast(owner.FindComponent(RplComponent));
1413  if (!rpl || !rpl.IsProxy())
1414  {
1415  Physics physics = owner.GetPhysics();
1416  // CallLater because GetOwner().GetPhysics() returns empty until GC finishes onPostInit
1417  GetGame().GetCallqueue().CallLater(SetExactMinImpulse, param1:owner);
1418  }
1419 
1420 #ifdef ENABLE_DIAG
1421  DiagMenu.RegisterBool(SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE,"","Log player damage","GameCode");
1422 
1423  if (System.IsCLIParam("logPlayerDamage"))
1424  DiagMenu.SetValue(SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE, true);
1425 #endif
1426  }
1427 
1428  //------------------------------------------------------------------------------------------------
1429  protected void SetExactMinImpulse(IEntity owner)
1430  {
1431  if (!owner)
1432  return;
1433 
1434  m_fMinImpulse = 80;
1435  Physics physics = owner.GetPhysics();
1436  if (physics)
1437  m_fMinImpulse = physics.GetMass();
1438  }
1439 
1440  //------------------------------------------------------------------------------------------------
1445  void ContactDamage(IEntity owner, IEntity other, Contact contact)
1446  {
1447  if (Math.AbsFloat(contact.GetRelativeNormalVelocityBefore()) > 3)
1448  {
1449  ComputeCollisionDamage(owner, other, contact);
1450 
1451  if (LocalGetLifeState() == ECharacterLifeState.ALIVE)
1452  ForceUnconsciousness(0.3);
1453  }
1454  }
1455 
1456  //------------------------------------------------------------------------------------------------
1457  protected void ComputeCollisionDamage(notnull IEntity owner, notnull IEntity other, notnull Contact contact)
1458  {
1459  // Do not recompute collisiondamage within the impulseDelay if it's lower than the highest one thus far
1460  float relativeNormalVelocityBefore = Math.AbsFloat(contact.GetRelativeNormalVelocityBefore());
1461  if (m_fHighestContact > relativeNormalVelocityBefore)
1462  return;
1463 
1464  m_fHighestContact = relativeNormalVelocityBefore;
1465 
1466  float momentumCharacterThreshold = m_fMinImpulse * 1 * KM_PER_H_TO_M_PER_S;
1467  float momentumCharacterDestroy = m_fMinImpulse * 100 * KM_PER_H_TO_M_PER_S;
1468  float damageScaleToCharacter = (momentumCharacterDestroy - momentumCharacterThreshold) * 0.0001;
1469 
1470  float impactMomentum = Math.AbsFloat(m_fMinImpulse * relativeNormalVelocityBefore);
1471 
1472  float damageValue = damageScaleToCharacter * (impactMomentum - momentumCharacterThreshold);
1473  if (damageValue <= 0)
1474  return;
1475 
1476  // We apply the collisiondamage only every 200 ms at most.
1477  // If a bigger collision happens within this time, this collisions Contact data will overwrite the previous collision, but does not reset the remaining time until it is applied.
1478  int impulseDelay = 200;
1479  int remainingTime = GetGame().GetCallqueue().GetRemainingTime(ApplyCollisionDamage);
1480  if (remainingTime == -1)
1481  {
1482  GetGame().GetCallqueue().CallLater(ApplyCollisionDamage, impulseDelay, false, contact, other, damageValue);
1483  }
1484  else
1485  {
1486  GetGame().GetCallqueue().Remove(ApplyCollisionDamage);
1487  GetGame().GetCallqueue().CallLater(ApplyCollisionDamage, remainingTime, false, contact, other, damageValue);
1488  }
1489  }
1490 
1491  //------------------------------------------------------------------------------------------------
1492  protected void ApplyCollisionDamage(Contact contact, IEntity other, float damageValue)
1493  {
1494  if (!GetOwner())
1495  return;
1496 
1497  array<HitZone> characterHitZones = {};
1498  GetAllHitZones(characterHitZones);
1499 
1500  // Apply collisionDamage only to the 6 nearest hitzones to the contactpoint
1501  int hitZonesReturnAmount = 6;
1502  GetNearestHitZones(contact.Position, characterHitZones, hitZonesReturnAmount);
1503 
1504  foreach (HitZone characterHitZone : characterHitZones)
1505  {
1506  if (characterHitZone.GetDamageState() == EDamageState.DESTROYED)
1507  continue;
1508 
1509  characterHitZone.HandleDamage(damageValue / hitZonesReturnAmount, EDamageType.COLLISION, other);
1510  }
1511 
1512  m_fHighestContact = 0;
1513  }
1514 
1515  //------------------------------------------------------------------------------------------------
1520  void GetNearestHitZones(vector worldPosition, notnull inout array<HitZone> nearestHitZones, int hitZonesReturnAmount)
1521  {
1522  array<vector> hitZonePositions = {};
1523  array<int> IDs = {};
1524  foreach (HitZone hitZone : nearestHitZones)
1525  {
1526  if (!hitZone.HasColliderNodes())
1527  continue;
1528 
1529  IDs.Clear();
1530  hitZone.GetColliderIDs(IDs);
1531  foreach (int ID : IDs)
1532  {
1533  vector colliderPos[4];
1534  GetOwner().GetPhysics().GetGeomWorldTransform(ID, colliderPos);
1535  vector relativePosition = worldPosition.InvMultiply4(colliderPos);
1536  vector hitZonePosition = {relativePosition.Length(), ID, 0};
1537  hitZonePositions.Insert(hitZonePosition);
1538  }
1539  }
1540 
1541  // Order hitZonePositions by distance from close to furthest
1542  hitZonePositions.Sort();
1543 
1544  array<int> closestIDs = {};
1545  for (int i; i < hitZonesReturnAmount; i++)
1546  {
1547  vector hitZonePosition = hitZonePositions[i];
1548  closestIDs.InsertAt(hitZonePosition[1], i);
1549  }
1550 
1551  nearestHitZones.Clear();
1552  GetHitZonesByColliderIDs(nearestHitZones, closestIDs);
1553  }
1554 
1555  //------------------------------------------------------------------------------------------------
1556  void HandleFallDamage(float damage)
1557  {
1558  array<HitZone> targetHitZones = {};
1559  GetHitZonesOfGroup(ECharacterHitZoneGroup.LEFTLEG, targetHitZones, true);
1560  GetHitZonesOfGroup(ECharacterHitZoneGroup.RIGHTLEG, targetHitZones, false);
1561 
1562  float overDamageCutOff = 50;
1563  if (damage > overDamageCutOff)
1564  GetHitZonesOfGroup(ECharacterHitZoneGroup.LOWERTORSO, targetHitZones, false);
1565 
1566  damage *= 2;
1567 
1568  foreach (HitZone hitZone : targetHitZones)
1569  hitZone.HandleDamage(damage/targetHitZones.Count(), EDamageType.COLLISION, GetOwner());
1570  }
1571 
1572  #ifdef ENABLE_DIAG
1573  //-----------------------------------------------------------------------------------------------------------
1574  protected override void OnDamage(notnull BaseDamageContext damageContext)
1575  {
1576  super.OnDamage(damageContext);
1577 
1578  if (DiagMenu.GetBool(SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE))
1579  {
1580  SCR_HitZone scriptedHz = SCR_HitZone.Cast(damageContext.struckHitZone);
1581  if (!scriptedHz)
1582  return;
1583 
1584  IEntity hzOwner = scriptedHz.GetOwner();
1585  if (!hzOwner)
1586  return;
1587 
1588  string instigatorName;
1589  int instigatorID = damageContext.instigator.GetInstigatorPlayerID();
1590  IEntity instigatorEntity = damageContext.instigator.GetInstigatorEntity();
1591 
1592  if (instigatorID > 0)
1593  {
1594  instigatorName = GetGame().GetPlayerManager().GetPlayerName(instigatorID);
1595  }
1596  else
1597  {
1598  ResourceName prefabName;
1599  if (instigatorEntity)
1600  {
1601  EntityPrefabData prefabData = instigatorEntity.GetPrefabData();
1602  if (prefabData)
1603  prefabName = prefabData.GetPrefabName();
1604  }
1605 
1606  if (prefabName.IsEmpty())
1607  {
1608  if (instigatorEntity)
1609  {
1610  instigatorName = ((instigatorEntity.GetID()).ToString());
1611  }
1612  else
1613  {
1614  instigatorName = instigatorEntity.ToString();
1615  }
1616  }
1617  else
1618  {
1619  TStringArray strs = new TStringArray;
1620  prefabName.Split("/", strs, true);
1621  instigatorName = ((instigatorEntity.GetID()).ToString()) + strs[strs.Count() - 1];
1622  }
1623  }
1624 
1625  string hzOwnerName;
1626  int hzOwnerID = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(hzOwner);
1627  if (hzOwnerID > 0)
1628  {
1629  hzOwnerName = GetGame().GetPlayerManager().GetPlayerName(hzOwnerID);
1630  }
1631  else
1632  {
1633  EntityPrefabData prefabData = hzOwner.GetPrefabData();
1634  ResourceName prefabName = prefabData.GetPrefabName();
1635 
1636  if (prefabName.IsEmpty())
1637  {
1638  hzOwnerName = ((hzOwner.GetID()).ToString());
1639  }
1640  else
1641  {
1642  TStringArray strs = new TStringArray;
1643  prefabName.Split("/", strs, true);
1644  hzOwnerName = ((hzOwner.GetID()).ToString()) + strs[strs.Count() - 1];
1645  }
1646  }
1647 
1648  if (EntityUtils.IsPlayer(instigatorEntity) || EntityUtils.IsPlayer(hzOwner))
1649  PrintFormat("HIT LOG: (%1) damaged (%2) - [Damage = %3, Speed = %4]", instigatorName, hzOwnerName, damageContext.damageValue, damageContext.impactVelocity);
1650  }
1651  }
1652 
1653  override void OnDelete(IEntity owner)
1654  {
1655  DisconnectFromDiagSystem(owner);
1656 
1657  super.OnDelete(owner);
1658  }
1659 
1660  //-----------------------------------------------------------------------------------------------------------
1661  void DiagInit(IEntity owner)
1662  {
1663  ConnectToDiagSystem(owner);
1664  // Register to debug menu
1665  DiagMenu.RegisterBool(SCR_DebugMenuID.DEBUGUI_CHARACTER_DAMAGE,"","Deal damage debug","Character");
1666  DiagMenu.SetValue(SCR_DebugMenuID.DEBUGUI_CHARACTER_DAMAGE,0);
1667  }
1668 
1669  //-----------------------------------------------------------------------------------------------------------
1670  override void OnDiag(IEntity owner, float timeSlice)
1671  {
1672  ProcessDebug(owner);
1673  }
1674 
1675  //------------------------------------------------------------------------------------------------
1676  void ProcessDebug(IEntity owner)
1677  {
1678  if (DiagMenu.GetBool(SCR_DebugMenuID.DEBUGUI_CHARACTER_DAMAGE))
1679  {
1680  // Only apply debug damages to currently controlled character
1681  IEntity playerEntity = GetGame().GetPlayerController().GetControlledEntity();
1682  if (playerEntity != owner)
1683  return;
1684 
1685  DbgUI.Text("See: GameCode >> Hit Zones >> Player HitZones for damages");
1686  DbgUI.Text("Applying damage from client is NOT possible");
1687  DbgUI.Text("Only conventional DOT damage are shown in debug");
1688  DbgUI.Text("Y to damage every hitZone for 4 damage");
1689  DbgUI.Text("U to reduce chest health by 10");
1690  DbgUI.Text("I to reduce right thigh health by 10");
1691  DbgUI.Text("O to reduce left thigh health by 10");
1692  DbgUI.Text("P to add bleeding to right thigh");
1693  DbgUI.Text("T to add bleeding to left thigh");
1694  DbgUI.Text("K to reset damage effect");
1695 
1696  vector hitPosDirNorm[3];
1697 
1698  if (Debug.KeyState(KeyCode.KC_Y))
1699  {
1700  Debug.ClearKey(KeyCode.KC_Y);
1701  AddParticularBleeding("RThigh", intensityFloat: Math.RandomFloat(0.9, 0.99));
1702  AddParticularBleeding("LThigh", intensityFloat: Math.RandomFloat(0.9, 0.99));
1703  AddParticularBleeding("Chest", intensityFloat: Math.RandomFloat(0.9, 0.99));
1704  AddParticularBleeding("Head", intensityFloat: Math.RandomFloat(0.9, 0.99));
1705  AddParticularBleeding("LArm", intensityFloat: Math.RandomFloat(0.9, 0.99));
1706  AddParticularBleeding("Abdomen", intensityFloat: Math.RandomFloat(0.9, 0.99));
1707  AddParticularBleeding("RArm", intensityFloat: Math.RandomFloat(0.9, 0.99));
1708  }
1709  if (Debug.KeyState(KeyCode.KC_U))
1710  {
1711  Debug.ClearKey(KeyCode.KC_U);
1712  Print(HealHitZones(10, false, true));
1713  }
1714  if (Debug.KeyState(KeyCode.KC_I))
1715  {
1716  Debug.ClearKey(KeyCode.KC_I);
1717 
1718  SCR_DamageContext damageContext = new SCR_DamageContext(EDamageType.TRUE, 10, hitPosDirNorm, GetGame().GetPlayerController().GetControlledEntity(), GetHitZoneByName("RThigh"), Instigator.CreateInstigator(GetGame().GetPlayerController().GetControlledEntity()), null, -1, -1);
1719  HandleDamage(damageContext);
1720  }
1721  if (Debug.KeyState(KeyCode.KC_O))
1722  {
1723  Debug.ClearKey(KeyCode.KC_O);
1724 
1725  SCR_DamageContext damageContext = new SCR_DamageContext(EDamageType.TRUE, 10, hitPosDirNorm, GetGame().GetPlayerController().GetControlledEntity(), GetHitZoneByName("LThigh"), Instigator.CreateInstigator(GetGame().GetPlayerController().GetControlledEntity()), null, -1, -1);
1726  HandleDamage(damageContext);
1727  }
1728  if (Debug.KeyState(KeyCode.KC_P))
1729  {
1730  Debug.ClearKey(KeyCode.KC_P);
1731  AddParticularBleeding("RThigh");
1732  }
1733  if (Debug.KeyState(KeyCode.KC_T))
1734  {
1735  Debug.ClearKey(KeyCode.KC_T);
1736  AddParticularBleeding("LThigh");
1737  }
1738  if (Debug.KeyState(KeyCode.KC_K))
1739  {
1740  Debug.ClearKey(KeyCode.KC_K);
1741  FullHeal();
1742  }
1743  }
1744  }
1745 #endif
1746 };
ECharacterHitZoneGroup
ECharacterHitZoneGroup
Definition: SCR_CharacterDamageManagerComponent.c:1
SCR_BaseGameMode
Definition: SCR_BaseGameMode.c:137
GetState
EEditableEntityState GetState()
Definition: SCR_BaseEntitiesEditorUIEffect.c:7
BaseDamageContext
Definition: BaseDamageContext.c:12
m_bPermitUnconsciousness
protected bool m_bPermitUnconsciousness
Definition: SCR_GameModeHealthSettings.c:14
SCR_Enum
Definition: SCR_Enum.c:1
CharacterInputContext
Definition: CharacterInputContext.c:12
UPPERTORSO
@ UPPERTORSO
Definition: SCR_CharacterDamageManagerComponent.c:4
m_fMinImpulse
protected float m_fMinImpulse
Definition: SCR_VehicleDamageManagerComponent.c:190
SCR_CharacterHitZone
Definition: SCR_CharacterHitZone.c:55
ECharacterLifeState
ECharacterLifeState
Definition: ECharacterLifeState.c:12
HitZone
Definition: HitZone.c:12
OnDamage
override protected void OnDamage(notnull BaseDamageContext damageContext)
Definition: SCR_ArmorDamageManagerComponent.c:11
GetGame
ArmaReforgerScripted GetGame()
Definition: game.c:1424
SCR_SoundEvent
Definition: SCR_SoundEvent.c:1
OnDelete
override void OnDelete(IEntity owner)
Definition: SCR_CampaignBuildingCompositionComponent.c:538
RIGHTARM
@ RIGHTARM
Definition: SCR_CharacterDamageManagerComponent.c:7
EDamageState
EDamageState
Definition: EDamageState.c:12
desc
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
Definition: SCR_RespawnBriefingComponent.c:17
RplRpc
SCR_AchievementsHandlerClass ScriptComponentClass RplRpc(RplChannel.Reliable, RplRcver.Owner)] void UnlockOnClient(AchievementId achievement)
Definition: SCR_AchievementsHandler.c:11
GetPlayerController
proto external PlayerController GetPlayerController()
Definition: SCR_PlayerDeployMenuHandlerComponent.c:307
Instigator
Definition: Instigator.c:6
SCR_CharacterBloodHitZone
Blood - does not receive damage directly, only via scripted events.
Definition: SCR_CharacterHitZone.c:480
GetGameMode
SCR_BaseGameMode GetGameMode()
Definition: SCR_BaseGameModeComponent.c:15
SCR_RegeneratingHitZone
Definition: SCR_CharacterHitZone.c:312
LOWERTORSO
@ LOWERTORSO
Definition: SCR_CharacterDamageManagerComponent.c:5
IsProxy
protected bool IsProxy()
Definition: SCR_CampaignBuildingCompositionComponent.c:456
ECharacterDamageState
ECharacterDamageState
Definition: SCR_CharacterHitZone.c:1
GetInstigator
BaseProjectileComponentClass GameComponentClass GetInstigator()
HEAD
@ HEAD
Definition: SCR_CharacterDamageManagerComponent.c:3
LEFTLEG
@ LEFTLEG
Definition: SCR_CharacterDamageManagerComponent.c:8
Attribute
typedef Attribute
Post-process effect of scripted camera.
SCR_CharacterDamageManagerComponent
Definition: SCR_CharacterDamageManagerComponent.c:18
SCR_DamageManagerComponentClass
enum ECharacterHitZoneGroup SCR_DamageManagerComponentClass
OnDiag
event protected void OnDiag(IEntity owner, float timeslice)
GetOwner
IEntity GetOwner()
Owner entity of the fuel tank.
Definition: SCR_FuelNode.c:128
m_fRegenScale
protected float m_fRegenScale
Definition: SCR_GameModeHealthSettings.c:11
ECharacterResilienceState
ECharacterResilienceState
Definition: SCR_CharacterHitZone.c:20
GetControlledEntity
SCR_EditableEntityComponent GetControlledEntity()
Returns the controlled entity or null if none.
Definition: SCR_EditablePlayerDelegateComponent.c:86
SCR_HitZone
Definition: SCR_HitZone.c:1
SCR_DamageContext
Definition: SCR_DamageContext.c:7
LEFTARM
@ LEFTARM
Definition: SCR_CharacterDamageManagerComponent.c:6
m_fTourniquetStrengthMultiplier
private float m_fTourniquetStrengthMultiplier
Definition: SCR_GameModeHealthSettings.c:23
type
EDamageType type
Definition: SCR_DestructibleTreeV2.c:32
EDamageType
EDamageType
Definition: EDamageType.c:12
EHitReactionType
EHitReactionType
Definition: EHitReactionType.c:12
SCR_CharacterHeadHitZone
Definition: SCR_CharacterHitZone.c:560
params
Configs ServerBrowser KickDialogs params
Definition: SCR_NotificationSenderComponent.c:24
EntityUtils
Definition: EntityUtils.c:12
SCR_BleedingHitZoneParameters
Definition: SCR_CharacterHitZone.c:40
ECharacterBloodState
ECharacterBloodState
Definition: SCR_CharacterHitZone.c:13
RIGHTLEG
@ RIGHTLEG
Definition: SCR_CharacterDamageManagerComponent.c:9
SCR_DebugMenuID
SCR_DebugMenuID
This enum contains all IDs for DiagMenu entries added in script.
Definition: DebugMenuID.c:3
EBandagingAnimationBodyParts
EBandagingAnimationBodyParts
Definition: SCR_CharacterHitZone.c:27
GameMaterial
Definition: GameMaterial.c:12
category
params category
Definition: SCR_VehicleDamageManagerComponent.c:180