Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_CharacterDamageManagerComponent.c
Go to the documentation of this file.
11
12//------------------------------------------------------------------------------------------------
13class SCR_CharacterDamageManagerComponentClass: SCR_ExtendedDamageManagerComponentClass
14{
15};
16
17//------------------------------------------------------------------------------------------------
18class SCR_CharacterDamageManagerComponent : SCR_ExtendedDamageManagerComponent
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 //equivalent of colliding with s105 while it is moving at around ~3.5m/s (~10km/h)
27 protected const int MIN_OTHER_MOMENTUM = 3000;
28
29 // time for resetting minimum impulse.
30 protected const int MINIMUM_IMPULSE_RESET_TIME = 2500;
31
32 // Physics variables
33 protected float m_fHighestContact = 0;
34 protected float m_fMinImpulse;
35 protected float m_fWaterFallDamageMultiplier = 0.33;
37
38 // Fall damage
39 protected float m_fVehicleFallTriggerVelocity = -2.0;
40 protected float m_fVehicleFallDamage = 200.0;
41
42 // Static array for all limbs
43 static ref array<ECharacterHitZoneGroup> LIMB_GROUPS;
44 static const ref array<ECharacterHitZoneGroup> EXTREMITY_LIMB_GROUPS = {ECharacterHitZoneGroup.LEFTARM, ECharacterHitZoneGroup.RIGHTARM, ECharacterHitZoneGroup.LEFTLEG, ECharacterHitZoneGroup.RIGHTLEG};
45
46 //replicated arrays for clients
47 protected ref array<ECharacterHitZoneGroup> m_aTourniquettedGroups;
48 protected ref array<ECharacterHitZoneGroup> m_aSalineBaggedGroups;
49 protected ref array<int> m_aBeingHealedGroup;
50 protected ref array<float> m_aGroupBleedingRates;
51
54 protected ref array<HitZone> m_aBleedingHitZones;
56 protected SCR_CharacterResilienceHitZone m_pResilienceHitZone;
58
59 // audio
61
62 static SCR_GameModeHealthSettings s_HealthSettings;
63
64 protected bool m_bDOTScaleChangedByGM;
68
69 // TODO: Move these attributes to prefab data to save some memory
70 [Attribute(defvalue: "", uiwidget: UIWidgets.ResourceNamePicker, desc: "Bleeding particle effect", params: "ptc", precision: 3, category: "Bleeding")]
72
73 [Attribute(defvalue: "0.5", uiwidget: UIWidgets.Slider, desc: "Bleeding particle effect rate scale", params: "0 5 0.001", precision: 3, category: "Bleeding")]
75
76 [Attribute(defvalue: "1", uiwidget: UIWidgets.Slider, desc: "Character bleeding rate multiplier", params: "0 5 0.001", precision: 3, category: "Bleeding")]
77 protected float m_fDOTScale;
78
79 [Attribute(defvalue: "1", uiwidget: UIWidgets.Slider, desc: "Character regeneration rate multiplier", params: "0 5 0.001", precision: 3, category: "Regeneration")]
80 protected float m_fRegenScale;
81
82 [Attribute("0.3", UIWidgets.Auto, "Resilience regeneration scale while unconscious\n[x * 100%]")]
84
85 [Attribute(defvalue: "true", uiwidget: UIWidgets.CheckBox, desc: "Whether unconsciousness is allowed", category: "Unconsciousness")]
87
88 [Attribute(defvalue: "0.05", uiwidget: UIWidgets.Slider, desc: "Affects how much the bleeding is reduced", params: "0 1 0.001", precision: 3, category: "Bleeding")]
90
91 [Attribute(defvalue: "4", uiwidget: UIWidgets.EditBox, desc: "When higher, less damage is needed to reach max aimingDamage", category: "Damage penalties")]
92 protected float m_fAimingDamageMultiplier;
93
94 [Attribute(defvalue: "3", uiwidget: UIWidgets.EditBox, desc: "When higher, less damage is needed to reach m_fMaxMovementDamage", category: "Damage penalties")]
96
97 [Attribute(defvalue: "0.8", uiwidget: UIWidgets.EditBox, desc: "Maximum movement damage. 1 Means max-damage/immobilized", category: "Damage penalties")]
98 protected float m_fMaxMovementDamage;
99
100 [Attribute(defvalue: "true", uiwidget: UIWidgets.CheckBox, desc: "Allow this character to show bleeding effects on any clothing items when damaged or bleeding", category: "Bleeding")]
101 protected bool m_bAllowBloodyClothes;
102
103 [Attribute(defvalue: "0.1", desc: "Poison buildup speed factor", params: "0.01 1 0.01")]
104 protected float m_fPoisonBuildupFactor;
105
106 //-----------------------------------------------------------------------------------------------------------
108 {
110 }
111
112
113 bool IsFallingVehicle(IEntity owner, IEntity other, Contact contact)
114 {
115 // Is it falling?
116 if (contact.VelocityBefore2[1] > m_fVehicleFallTriggerVelocity)
117 return false;
118
119 // Is it a vehicle
120 if (!Vehicle.Cast(other))
121 return false;
122
123 ChimeraCharacter character = ChimeraCharacter.Cast(owner);
124 if (!character)
125 return false;
126
127 // Is the contact above the character?
128 vector characterPosition = character.AimingPosition();
129 vector impactPosition = contact.Position;
130 return impactPosition[1] > characterPosition[1];
131 }
132
133 //-----------------------------------------------------------------------------------------------------------
135 protected bool ShouldBeUnconscious()
136 {
137 HitZone bloodHZ = GetBloodHitZone();
138 if (!bloodHZ)
139 return false;
140
141 ECharacterBloodState bloodState = bloodHZ.GetDamageState();
142 if (bloodHZ.GetDamageStateThreshold(bloodState) <= bloodHZ.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS))
143 return true;
144
145 HitZone resilienceHZ = GetResilienceHitZone();
146 if (!resilienceHZ)
147 return false;
148
149 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
150 if (!character)
151 return false;
152
153 CharacterControllerComponent controller = character.GetCharacterController();
154 if (!controller)
155 return false;
156
157 ECharacterResilienceState resilienceState = resilienceHZ.GetDamageState();
158
159 if (controller.IsUnconscious())
160 {
161 if (resilienceHZ.GetDamageStateThreshold(resilienceState) <= resilienceHZ.GetDamageStateThreshold(ECharacterResilienceState.WEAKENED))
162 return true;
163 }
164 else
165 {
166 if (resilienceHZ.GetDamageStateThreshold(resilienceState) <= resilienceHZ.GetDamageStateThreshold(ECharacterResilienceState.UNCONSCIOUS))
167 return true;
168 }
169
170 return false;
171 }
172
173 //-----------------------------------------------------------------------------------------------------------
175 {
176 bool unconscious = ShouldBeUnconscious();
177
178 // If unconsciousness is not allowed, kill character
179 // Also kill the character if the blood state is not high enough for being unconsciousness
180 if (unconscious)
181 {
182 if (!GetPermitUnconsciousness() || (m_pBloodHitZone && m_pBloodHitZone.GetDamageState() == ECharacterBloodState.DESTROYED))
183 {
185 return;
186 }
187 else if (s_HealthSettings && s_HealthSettings.GetIfKillIndefiniteUnconscious() && IsIndefinitelyUnconscious())
188 {
190 return;
191 }
192 }
193
194 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
195 if (!character)
196 return;
197
198 CharacterControllerComponent controller = character.GetCharacterController();
199 if (!controller)
200 return;
201
202 controller.SetUnconscious(unconscious);
203 }
204
205 //-----------------------------------------------------------------------------------------------------------
208 void ForceUnconsciousness(float resilienceHealth = 0)
209 {
211 return;
212
213 HitZone resilienceHZ = GetResilienceHitZone();
214 if (!resilienceHZ)
215 return;
216
217 resilienceHZ.HandleDamage(resilienceHZ.GetMaxHealth() - (resilienceHZ.GetMaxHealth() * resilienceHealth), EDamageType.TRUE, null);
219 }
220
221 //------------------------------------------------------------------------------------------------
222 void OnLifeStateChanged(ECharacterLifeState previousLifeState, ECharacterLifeState newLifeState, bool isJIP)
223 {
224 if(isJIP)
225 return;
226
227 if (newLifeState == ECharacterLifeState.INCAPACITATED)
228 {
230 }
231 else if (newLifeState == ECharacterLifeState.DEAD)
232 {
233 SoundDeath(previousLifeState);
235 }
236 }
237
238 //-----------------------------------------------------------------------------------------------------------
240 void InsertArmorData(notnull SCR_CharacterHitZone charHitZone, SCR_ArmoredClothItemData attributes)
241 {
244
245 m_mClothItemDataMap.Insert(charHitZone, attributes);
246 }
247
248 //-----------------------------------------------------------------------------------------------------------
250 void RemoveArmorData(notnull SCR_CharacterHitZone charHitZone)
251 {
253 return;
254
255 if (m_mClothItemDataMap.Contains(charHitZone))
256 m_mClothItemDataMap.Remove(charHitZone);
257
258 if (m_mClothItemDataMap.IsEmpty())
259 m_mClothItemDataMap = null;
260 }
261
262 //-----------------------------------------------------------------------------------------------------------
264 void UpdateArmorDataMap(notnull SCR_ArmoredClothItemData armorAttr, bool remove)
265 {
266 foreach (string hitZoneName : armorAttr.m_aProtectedHitZones)
267 {
268 SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(GetHitZoneByName(hitZoneName));
269 if (hitZone)
270 {
271 if (remove)
272 {
273 RemoveArmorData(hitZone);
274 continue;
275 }
276
277 SCR_ArmoredClothItemData armoredClothItemData;
278 armoredClothItemData = new SCR_ArmoredClothItemData(
279 armorAttr.m_eProtection,
280 armorAttr.m_aProtectedHitZones,
281 armorAttr.m_aProtectedDamageTypes,
282 armorAttr.m_MaterialResourceName
283 );
284
285 InsertArmorData(hitZone, armoredClothItemData);
286 }
287 }
288 }
289
290 //-----------------------------------------------------------------------------------------------------------
292 {
293 SCR_CharacterHitZone charHitZone = SCR_CharacterHitZone.Cast(struckHitzone);
294 if (!charHitZone)
295 return null;
296
297 SCR_ArmoredClothItemData armorData = GetArmorData(charHitZone);
298 if (!armorData)
299 return null;
300
301 if (!armorData.m_Material)
302 return null;
303
304 return armorData.m_Material.GetResource().ToBaseContainer();
305 }
306
307 //-----------------------------------------------------------------------------------------------------------
309 SCR_ArmoredClothItemData GetArmorData(notnull SCR_CharacterHitZone charHitZone)
310 {
312 return null;
313
314 if (m_mClothItemDataMap.Contains(charHitZone))
315 return m_mClothItemDataMap.Get(charHitZone);
316
317 return null;
318 }
319
320 //-----------------------------------------------------------------------------------------------------------
322 float GetArmorProtection(notnull SCR_CharacterHitZone charHitZone, EDamageType damageType)
323 {
324 SCR_ArmoredClothItemData localArmorData = GetArmorData(charHitZone);
325 if (!localArmorData)
326 return 0;
327
328 foreach (EDamageType protectedType : localArmorData.m_aProtectedDamageTypes)
329 {
330 if (damageType == protectedType)
331 return localArmorData.m_eProtection;
332 }
333
334 return 0;
335 }
336
337 //-----------------------------------------------------------------------------------------------------------
339 void ArmorHitEventEffects(float damage)
340 {
341 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
342 if (!character)
343 return;
344
345 CharacterControllerComponent controller = character.GetCharacterController();
346 if (!controller)
347 return;
348
349 CharacterInputContext context = controller.GetInputContext();
350 if (context)
351 context.SetHit(EHitReactionType.HIT_REACTION_LIGHT, 0);
352 }
353
354 //-----------------------------------------------------------------------------------------------------------
356 void ArmorHitEventDamage(EDamageType type, float damage, IEntity instigator)
357 {
359 m_pResilienceHitZone.HandleDamage(damage, type, instigator);
360 }
361
362 //-----------------------------------------------------------------------------------------------------------
363 override void OnDamageEffectAdded(notnull SCR_DamageEffect dmgEffect)
364 {
365 super.OnDamageEffectAdded(dmgEffect);
366
367 if (dmgEffect.GetAffectedHitZone().IsProxy())
368 return;
369
370 if (dmgEffect.GetDamageType() == EDamageType.HEALING)
371 SoundHeal();
372
373 if (dmgEffect.GetDamageType() == EDamageType.BLEEDING || !DotDamageEffect.Cast(dmgEffect))
374 {
375 if (GetState() == EDamageState.DESTROYED)
376 return;
377
378 //Terminate passive regeneration on bloodHZ when bleeding effect is added
379 array<ref SCR_PersistentDamageEffect> effects = {};
380 FindAllDamageEffectsOfTypeOnHitZone(SCR_PassiveHitZoneRegenDamageEffect, m_pBloodHitZone, effects);
381
382 foreach (SCR_PersistentDamageEffect effect : effects)
383 {
384 effect.Terminate();
385 }
386 }
387 }
388
389 //-----------------------------------------------------------------------------------------------------------
390 override void OnDamageEffectRemoved(notnull SCR_DamageEffect dmgEffect)
391 {
392 super.OnDamageEffectRemoved(dmgEffect);
393
394 if (dmgEffect.GetAffectedHitZone().IsProxy())
395 return;
396
397 EDamageType localDamageType = dmgEffect.GetDamageType();
398
399 if (localDamageType == EDamageType.BLEEDING || !DotDamageEffect.Cast(dmgEffect))
400 {
401 //If no longer bleeding, plan blood regeneration
402 if (!IsBleeding())
404 }
405
406 // if a healing effect is removed, it may mean all damage was healed. So try to clear the history in this case
407 if (localDamageType == EDamageType.HEALING || localDamageType == EDamageType.REGENERATION)
409 }
410
411 //------------------------------------------------------------------------------------------------
413 override bool HijackDamageHandling(notnull BaseDamageContext damageContext)
414 {
415 const SCR_HitZone hitZone = SCR_HitZone.Cast(damageContext.struckHitZone);
416 if (hitZone && !DotDamageEffect.Cast(damageContext.damageEffect))
417 hitZone.ApplyDamagePassRules(damageContext);
418
419 // Handle falldamage. Falldamage is applied to defaultHZ, so it's propegated down to physical hitZones manually, then back up to the health HZ like normal damage.
420 if (damageContext.damageType == EDamageType.COLLISION && damageContext.struckHitZone == GetDefaultHitZone())
421 {
422 if (damageContext.damageValue > damageContext.struckHitZone.GetDamageThreshold())
423 HandleAnimatedFallDamage(damageContext.damageValue);
424
425 return true;
426 }
427
428 return false;
429 }
430
431 //------------------------------------------------------------------------------------------------
434 protected void KnockOffTheHelmet(notnull BaseDamageContext damageContext)
435 {
436 SCR_CharacterHeadHitZone headHZ = SCR_CharacterHeadHitZone.Cast(damageContext.struckHitZone);
437 if (!headHZ)
438 return;
439
440 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
441 if (!character)
442 return;
443
444 CharacterControllerComponent controller = character.GetCharacterController();
445 if (!controller)
446 return;
447
448 SCR_InventoryStorageManagerComponent storageMgr = SCR_InventoryStorageManagerComponent.Cast(controller.GetInventoryStorageManager());
449 if (!storageMgr)
450 return;
451
452 SCR_CharacterInventoryStorageComponent characterStorage = storageMgr.GetCharacterStorage();
453 if (!characterStorage)
454 return;
455
456 SCR_HeadgearInventoryItemComponent hatIIC = SCR_HeadgearInventoryItemComponent.Cast(characterStorage.GetItemFromLoadoutSlot(new LoadoutHeadCoverArea()));
457 if (hatIIC)
458 hatIIC.DetachHelmet(damageContext.damageValue, damageContext.damageType, damageContext.hitDirection, damageContext.hitPosition, character);
459 }
460
461 //------------------------------------------------------------------------------------------------
465 bool SynchronizedSoundEvent(typename effectType)
466 {
467 array<ref SCR_PersistentDamageEffect> persistentEffects = {};
468 GetPersistentEffects(persistentEffects);
469 foreach (int i, SCR_PersistentDamageEffect effect : persistentEffects)
470 {
471 if (effect.Type() != effectType)
472 continue;
473
474 return SynchronizedSoundEvent(effect, i);
475 }
476
477 return false;
478 }
479
480 //------------------------------------------------------------------------------------------------
485 bool SynchronizedSoundEvent(notnull SCR_PersistentDamageEffect broadcastingEffect, int effectId = -1)
486 {
487 if (effectId < 0)
488 {
489 array<ref SCR_PersistentDamageEffect> persistentEffects = {};
490 GetPersistentEffects(persistentEffects);
491 foreach (int i, SCR_PersistentDamageEffect effect : persistentEffects)
492 {
493 if (effect != broadcastingEffect)
494 continue;
495
496 effectId = i;
497 break;
498 }
499 }
500
501 if (effectId < 0)
502 return false;
503
504 if (!broadcastingEffect.ExecuteSynchronizedSoundPlayback(this))
505 return false;
506
507 Rpc(Do_SynchronizedSoundEvent, effectId);
508 return true;
509 }
510
511 //------------------------------------------------------------------------------------------------
513 [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
514 protected void Do_SynchronizedSoundEvent(int effectId)
515 {
516 array<ref SCR_PersistentDamageEffect> persistentEffects = {};
517 GetPersistentEffects(persistentEffects);
518 foreach (int i, SCR_PersistentDamageEffect effect : persistentEffects)
519 {
520 if (i != effectId)
521 continue;
522
523 effect.ExecuteSynchronizedSoundPlayback(this);
524 break;
525 }
526 }
527
528 //------------------------------------------------------------------------------------------------
529 void SoundHit(bool critical, EDamageType damageType)
530 {
531 // Ignore if knocked out or dead
532 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
533 if (!character)
534 return;
535
536 if (character.GetCharacterController().GetLifeState() != ECharacterLifeState.ALIVE)
537 return;
538
540 m_CommunicationSound.SoundEventHit(critical, damageType);
541 }
542
543 //------------------------------------------------------------------------------------------------
546 {
547 if (m_CommunicationSound && !m_CommunicationSound.IsPlaying())
548 m_CommunicationSound.SoundEventDeath(true);
549 }
550
551 //------------------------------------------------------------------------------------------------
553 void SoundDeath(int previousLifestate)
554 {
555 bool silent;
556
557 // Silent if killed with headshot
558 if (m_pHeadHitZone && m_pHeadHitZone.GetDamageState() == EDamageState.DESTROYED)
559 silent = true;
560
561 // Silent if already knocked out
562 if (previousLifestate == ECharacterLifeState.INCAPACITATED)
563 silent = true;
564
566 m_CommunicationSound.SoundEventDeath(silent);
567 }
568
569 //------------------------------------------------------------------------------------------------
571 {
572 // Ignore if knocked out or dead
573 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
574 if (character)
575 {
576 if (character.GetCharacterController().GetLifeState() != ECharacterLifeState.ALIVE)
577 return;
578 }
579
581 m_CommunicationSound.DelayedSoundEventPriority(SCR_SoundEvent.SOUND_VOICE_PAIN_RELIEVE, SCR_ECommunicationSoundEventPriority.SOUND_PAIN_RELIEVE, SCR_CommunicationSoundComponent.DEFAULT_EVENT_PRIORITY_DELAY);
582 }
583
584 //-----------------------------------------------------------------------------------------------------------
587 {
588 float bleedingRate;
589 if (m_pBloodHitZone)
590 bleedingRate = DEATH_BLEEDOUT_SCALE * m_pBloodHitZone.GetTotalBleedingAmount();
591
592 float delay;
593 if (bleedingRate > 0)
594 delay = m_pBloodHitZone.GetHealth() / bleedingRate;
595
596 // Damage over time is not simulated after character death
597 GetGame().GetCallqueue().Remove(RemoveAllBleedingParticles);
598 GetGame().GetCallqueue().CallLater(RemoveAllBleedingParticles, delay * 1000);
599 }
600
603 //-----------------------------------------------------------------------------------------------------------
604 void AddBloodToClothes(notnull SCR_CharacterHitZone hitZone, float immediateBloodEffect)
605 {
606 if (!m_bAllowBloodyClothes || immediateBloodEffect < 1)
607 return;
608
609 EquipedLoadoutStorageComponent loadoutStorage = EquipedLoadoutStorageComponent.Cast(GetOwner().FindComponent(EquipedLoadoutStorageComponent));
610 if (!loadoutStorage)
611 return;
612
613 array<ref LoadoutAreaType> bleedingAreas = hitZone.GetBleedingAreas();
614
615 IEntity clothEntity;
616 ParametricMaterialInstanceComponent materialComponent;
617 foreach (LoadoutAreaType bleedingArea : bleedingAreas)
618 {
619 clothEntity = loadoutStorage.GetClothFromArea(bleedingArea.Type());
620 if (!clothEntity)
621 continue;
622
624 if (!materialComponent)
625 continue;
626
627 float newBloodValue = materialComponent.GetUserParam2() + Math.Ceil(immediateBloodEffect * 0.1);
628 materialComponent.SetUserParam2(Math.Clamp(newBloodValue, 1, 255));
629 }
630 }
631
632 //------------------------------------------------------------------------------------------------
633 override bool CanBeHealed(bool ignoreHealingDOT = true)
634 {
635 array<ref SCR_PersistentDamageEffect> effects = {};
636 GetPersistentEffects(effects);
637
638 // if there is any physical damage or a bleeding, you can be healed
639 array<HitZone> hitZones = {};
640 GetAllHitZonesInHierarchy(hitZones);
641 foreach (HitZone hitZone : hitZones)
642 {
643 if (hitZone && hitZone.GetDamageState() != EDamageState.UNDAMAGED)
644 return true;
645 }
646
647 if (IsBleeding())
648 return true;
649
650 // if there is no physical damage or bleeding, healing DOT could still classify you as injured if ignoreHealingDOT is false
651 if (ignoreHealingDOT)
652 return false;
653
654 if (IsDamageEffectPresent(SCR_PhysicalHitZonesRegenDamageEffect) || IsDamageEffectPresent(SCR_PassiveHitZoneRegenDamageEffect))
655 return true;
656
657 return false;
658 }
659
660 //-----------------------------------------------------------------------------------------------------------
663 override void FullHeal(bool ignoreHealingDOT = true)
664 {
665 if (!ignoreHealingDOT)
666 {
667 array<ref SCR_PersistentDamageEffect> effects = {};
668 GetPersistentEffects(effects);
669
670 foreach(SCR_PersistentDamageEffect effect : effects)
671 {
672 effect.Terminate();
673 }
674 }
675 else
676 {
678 }
679
680 super.FullHeal(ignoreHealingDOT);
681 }
682
683 //-----------------------------------------------------------------------------------------------------------
684 protected void TryClearDamageHistory()
685 {
686 // Upon death, do not clear damage history. It will instead be cleared OnDelete()
687 if (GetState() == EDamageState.DESTROYED)
688 {
689 // Dead bodies keep their history for as long as they aren't despawned. This is memory expensive. To prevent it, we clear history after x seconds
690 GetGame().GetCallqueue().CallLater(ClearDamageHistory, 1500);
691 }
692 else if (GetState() == EDamageState.UNDAMAGED)
693 {
694 // do not clear damage history if there are positive OR negative effects left on the character
695 if (CanBeHealed(false))
696 return;
697
698 ClearDamageHistory();
699 }
700 }
701
702 //-----------------------------------------------------------------------------------------------------------
704 {
706 }
707
708 //-----------------------------------------------------------------------------------------------------------
713
714 //------------------------------------------------------------------------------------------------
715 // Scale for effects that become worsened with blood level
717 {
718 if (!m_pBloodHitZone)
719 return 1;
720
721 float criticalBloodLevel = m_pBloodHitZone.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS);
722 if (criticalBloodLevel >= 1)
723 return 0;
724
725 float currentBloodLevel = m_pBloodHitZone.GetDamageStateThreshold(m_pBloodHitZone.GetDamageState());
726 if (currentBloodLevel < criticalBloodLevel)
727 return 0;
728
729 float resilienceRegenScale = (currentBloodLevel - criticalBloodLevel) / (1 - criticalBloodLevel);
730
731 // Change regen scale when unconscious
732 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
733 if (character)
734 {
735 if (character.GetCharacterController().GetLifeState() == ECharacterLifeState.INCAPACITATED)
736 resilienceRegenScale *= m_fUnconsciousRegenerationScale;
737 }
738
739 return resilienceRegenScale;
740 }
741
742 //-----------------------------------------------------------------------------------------------------------
743 void SetBleedingScale(float rate, bool changed)
744 {
745 m_fDOTScale = rate;
746 m_bDOTScaleChangedByGM = changed;
747 }
748
749 //------------------------------------------------------------------------------------------------
751 {
753 return m_fDOTScale;
754
755 return s_HealthSettings.GetBleedingScale();
756 }
757
758 //-----------------------------------------------------------------------------------------------------------
759 void SetRegenScale(float rate, bool changed)
760 {
761 m_fRegenScale = rate;
762 m_bRegenScaleChangedByGM = changed;
763 }
764
765 //------------------------------------------------------------------------------------------------
766 // Return base regen scale
768 {
770 return m_fRegenScale;
771
772 return s_HealthSettings.GetRegenScale();
773 }
774
775 //-----------------------------------------------------------------------------------------------------------
778 {
780 return false;
781
784
785 return s_HealthSettings.IsUnconsciousnessPermitted();
786 }
787
788 //-----------------------------------------------------------------------------------------------------------
790 protected bool IsIndefinitelyUnconscious(bool onlyPlayers = true)
791 {
792 if (onlyPlayers && !EntityUtils.IsPlayer(GetOwner()))
793 return false;
794
796 return false;
797
798 // if bloodHitZone is below unconscious threshold you will not wake up without a medic
799 if (m_pBloodHitZone.GetDamageState() >= ECharacterBloodState.UNCONSCIOUS)
800 return true;
801
802 // if unconscious and bleeding, you will not wake if your blood reaches below the ECharacterBloodState.UNCONSCIOUS treshold.
803 if (IsBleeding())
804 {
805 float timeToWake;
806 float timeToUnconscious = (m_pBloodHitZone.GetHealth() - (m_pBloodHitZone.GetDamageStateThreshold(ECharacterBloodState.UNCONSCIOUS) * m_pBloodHitZone.GetMaxHealth())) / m_pBloodHitZone.GetTotalBleedingAmount();
807
808 DotDamageEffect regenEffect = DotDamageEffect.Cast(FindDamageEffectOnHitZone(SCR_PassiveHitZoneRegenDamageEffect, GetResilienceHitZone()));
809 if (regenEffect)
810 timeToWake = ((m_pResilienceHitZone.GetDamageStateThreshold(ECharacterResilienceState.WEAKENED) * m_pResilienceHitZone.GetMaxHealth()) - m_pResilienceHitZone.GetHealth()) / -regenEffect.GetDPS();
811
812 if (timeToUnconscious < timeToWake)
813 return true;
814 }
815
816 return false;
817 }
818
819 //-----------------------------------------------------------------------------------------------------------
820 void SetPermitUnconsciousness(bool permit, bool changed)
821 {
824 }
825
826 //-----------------------------------------------------------------------------------------------------------
831
832 //-----------------------------------------------------------------------------------------------------------
837
838 //-----------------------------------------------------------------------------------------------------------
840 {
841 m_pResilienceHitZone = SCR_CharacterResilienceHitZone.Cast(hitZone);
842 }
843
844 //-----------------------------------------------------------------------------------------------------------
845 SCR_CharacterResilienceHitZone GetResilienceHitZone()
846 {
848 }
849
850 //-----------------------------------------------------------------------------------------------------------
852 {
854 }
855
856 //-----------------------------------------------------------------------------------------------------------
861
862 //-----------------------------------------------------------------------------------------------------------
867
868 //-----------------------------------------------------------------------------------------------------------
872 {
873 AddDamageEffect(effect);
874 }
875
876 //-----------------------------------------------------------------------------------------------------------
881 void AddBleedingEffectOnHitZone(notnull SCR_CharacterHitZone hitZone, int colliderDescriptorIndex = -1)
882 {
883 // This code is handled on authority only
884 if (hitZone.IsProxy())
885 return;
886
887 // In case bleeding is started outside of normal context, full health will prevent DOT. This block will circumvent this issue
888 float hitZoneDamageMultiplier = hitZone.GetHealthScaled();
889 float bleedingRate = hitZone.GetMaxBleedingRate() - hitZone.GetMaxBleedingRate() * hitZoneDamageMultiplier;
890
892 if (colliderDescriptorIndex == -1)
893 {
894 int descriptorCnt = hitZone.GetNumColliderDescriptors();
895 if (descriptorCnt == 1)
896 colliderDescriptorIndex = 0;
897 else if (descriptorCnt > 1)
898 colliderDescriptorIndex = Math.RandomInt(0, descriptorCnt - 1);
899 }
900
901 hitZoneBleeding.m_iColliderDescriptorIndex = colliderDescriptorIndex;
902 hitZoneBleeding.SetDPS(bleedingRate * GetBleedingScale());
903 hitZoneBleeding.SetMaxDuration(0);
904 hitZoneBleeding.SetDamageType(EDamageType.BLEEDING);
905 hitZoneBleeding.SetAffectedHitZone(hitZone);
906 hitZoneBleeding.SetInstigator(GetInstigator());
907 AddDamageEffect(hitZoneBleeding);
908 }
909
910 //-----------------------------------------------------------------------------------------------------------
912 void AddBleedingToArray(notnull HitZone hitZone)
913 {
916
917 if (!m_aBleedingHitZones.Contains(hitZone))
918 m_aBleedingHitZones.Insert(hitZone);
919 }
920
921 //-----------------------------------------------------------------------------------------------------------
923 void RemoveBleedingFromArray(notnull HitZone hitZone)
924 {
926 {
927 m_aBleedingHitZones.RemoveItem(hitZone);
928 if (m_aBleedingHitZones.IsEmpty())
929 m_aBleedingHitZones = null;
930 }
931 }
932
933 array<HitZone> GetBleedingHitZones()
934 {
935 return m_aBleedingHitZones;
936 }
937
938 //-----------------------------------------------------------------------------------------------------------
940 void CreateBleedingParticleEffect(notnull HitZone hitZone, int colliderDescriptorIndex)
941 {
942 if (System.IsConsoleApp())
943 return;
944
945 // Play Bleeding particle
946 if (m_sBleedingParticle.IsEmpty())
947 return;
948
950
951 // TODO: Blood traces on ground that should be left regardless of clothing, perhaps just delayed
952 SCR_CharacterHitZone characterHitZone = SCR_CharacterHitZone.Cast(hitZone);
953 if (!characterHitZone || characterHitZone.IsCovered())
954 return;
955
956 array<HitZone> groupHitZones = {};
957 GetHitZonesOfGroupFromOwner(characterHitZone.GetHitZoneGroup(), groupHitZones);
958 float bleedingRate;
959
960 foreach (HitZone groupHitZone : groupHitZones)
961 {
962 SCR_RegeneratingHitZone regenHitZone = SCR_RegeneratingHitZone.Cast(groupHitZone);
963 if (regenHitZone)
964 bleedingRate += regenHitZone.GetHitZoneDamageOverTime(EDamageType.BLEEDING);
965 }
966
967 if (bleedingRate == 0 || m_fBleedingParticleRateScale == 0)
968 return;
969
970 // Get bone node
971 vector transform[4];
972 int boneIndex;
973 int boneNode;
974 if (!hitZone.TryGetColliderDescription(GetOwner(), colliderDescriptorIndex, transform, boneIndex, boneNode))
975 return;
976
977 // Create particle emitter
979 spawnParams.Parent = GetOwner();
980 spawnParams.PivotID = boneNode;
981 ParticleEffectEntity particleEmitter = ParticleEffectEntity.SpawnParticleEffect(m_sBleedingParticle, spawnParams);
982 if (System.IsConsoleApp())
983 return;
984
985 if (!particleEmitter)
986 {
987 Print("Particle emitter: " + particleEmitter.ToString() + " There was a problem with creating the particle emitter: " + m_sBleedingParticle, LogLevel.WARNING);
988 return;
989 }
990
991 // Track particle emitter in array
994
995 m_mBleedingParticles.Insert(hitZone, particleEmitter);
996
997 // Play particles
998 Particles particles = particleEmitter.GetParticles();
999 if (particles)
1000 particles.MultParam(-1, EmitterParam.BIRTH_RATE, bleedingRate * m_fBleedingParticleRateScale);
1001 else
1002 Print("Particle: " + particles.ToString() + " Bleeding particle likely not created properly: " + m_sBleedingParticle, LogLevel.WARNING);
1003 }
1004
1005 //-----------------------------------------------------------------------------------------------------------
1008 {
1009 if (IsBleeding())
1010 return;
1011
1012 HitZone hitZone;
1013 array<HitZone> hitZones = {};
1014 array<ref SCR_PersistentDamageEffect> damageEffects = GetAllPersistentEffectsOfType(SCR_BleedingDamageEffect);
1015 foreach (SCR_PersistentDamageEffect effect : damageEffects)
1016 {
1017 hitZone = effect.GetAffectedHitZone();
1018 if (!hitZones.Contains(hitZone))
1019 hitZones.Insert(hitZone);
1020 }
1021
1022 foreach (HitZone hz : hitZones)
1023 {
1025 }
1026 }
1027
1028 //-----------------------------------------------------------------------------------------------------------
1033 {
1035 return;
1036
1037 ParticleEffectEntity particleEmitter = m_mBleedingParticles.Get(hitZone);
1038 if (particleEmitter)
1039 {
1040 particleEmitter.StopEmission();
1041 m_mBleedingParticles.Remove(hitZone);
1042 }
1043
1044 if (m_mBleedingParticles.IsEmpty())
1045 m_mBleedingParticles = null;
1046 }
1047
1048 //-----------------------------------------------------------------------------------------------------------
1051 {
1052 TerminateDamageEffectsOfType(SCR_BleedingDamageEffect);
1053 }
1054
1055 //-----------------------------------------------------------------------------------------------------------
1058 {
1059 if (!IsBleeding())
1060 return;
1061
1062 if (charHZGroup == ECharacterHitZoneGroup.VIRTUAL)
1063 return;
1064
1065 array<ref SCR_PersistentDamageEffect> effects = {};
1066 array<HitZone> hitZones = {};
1067 GetHitZonesOfGroupFromOwner(charHZGroup, hitZones);
1068
1069 foreach (HitZone hitZone : hitZones)
1070 {
1071 FindDamageEffectsOnHitZone(hitZone, effects);
1072 foreach (SCR_PersistentDamageEffect effect : effects)
1073 {
1074 if (effect.GetDamageType() == EDamageType.BLEEDING)
1075 TerminateDamageEffect(effect)
1076 }
1077
1078 effects.Clear();
1079 }
1080 }
1081
1082 //-----------------------------------------------------------------------------------------------------------
1084 void AddParticularBleeding(string hitZoneName = "Chest", ECharacterDamageState intensityEnum = ECharacterDamageState.WOUNDED, float intensityFloat = -1)
1085 {
1086 SCR_CharacterHitZone targetHitZone = SCR_CharacterHitZone.Cast( GetHitZoneByName(hitZoneName));
1087
1088 if (!targetHitZone || targetHitZone.IsProxy())
1089 return;
1090
1091 // If someone wants to add bleeding to undamaged hitZone, return
1092 if (intensityEnum == ECharacterDamageState.UNDAMAGED)
1093 return;
1094
1095 // if intensityFloat is unset use the enum instead
1096 if (intensityFloat < 0)
1097 targetHitZone.SetHealthScaled(targetHitZone.GetDamageStateThreshold(ECharacterDamageState.WOUNDED));
1098 else
1099 targetHitZone.SetHealthScaled(intensityFloat);
1100
1101 AddBleedingEffectOnHitZone(targetHitZone);
1102 }
1103
1104 //-----------------------------------------------------------------------------------------------------------
1106 SCR_CharacterHitZone GetRandomPhysicalHitZone(bool includeBleedingHZs = false)
1107 {
1108 array<HitZone> hitZones = {};
1109 array<HitZone> validHitZones = {};
1110 GetPhysicalHitZones(hitZones);
1111
1112 foreach (HitZone hitZone: hitZones)
1113 {
1114 //Is character hz
1115 SCR_CharacterHitZone characterHitZone = SCR_CharacterHitZone.Cast(hitZone);
1116 if (!characterHitZone)
1117 continue;
1118
1119 // skip over bleeding hitzones if includeBleedingHZs is true
1120 if (!includeBleedingHZs)
1121 {
1122 SCR_RegeneratingHitZone regenHitZone = SCR_RegeneratingHitZone.Cast(hitZone);
1123 if (!regenHitZone || regenHitZone.GetHitZoneDamageOverTime(EDamageType.BLEEDING))
1124 continue;
1125 }
1126
1127 validHitZones.Insert(characterHitZone);
1128 }
1129
1130 if (validHitZones.IsEmpty())
1131 return null;
1132
1133 SCR_CharacterHitZone hitZone = SCR_CharacterHitZone.Cast(validHitZones.GetRandomElement());
1134 return hitZone;
1135 }
1136
1137 //-----------------------------------------------------------------------------------------------------------
1143
1144 //-----------------------------------------------------------------------------------------------------------
1146 {
1147 array<HitZone> hitZones = {};
1148
1149 GetPhysicalHitZones(hitZones);
1150 SCR_CharacterHitZone charHitZone;
1151
1152 foreach (HitZone hitZone : hitZones)
1153 {
1154 charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1155 if (charHitZone && charHitZone.GetBodyPartToHeal() == bodyPartToBandage)
1156 return charHitZone.GetHitZoneGroup();
1157 }
1158
1159 return 0;
1160 }
1161
1162 //-----------------------------------------------------------------------------------------------------------
1164 {
1165 array<HitZone> hitZones = {};
1166
1167 GetPhysicalHitZones(hitZones);
1168 SCR_CharacterHitZone charHitZone;
1169
1170 foreach (HitZone hitZone : hitZones)
1171 {
1172 charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1173 if (charHitZone && charHitZone.GetHitZoneGroup() == hitZoneGroup)
1174 return charHitZone.GetBodyPartToHeal();
1175 }
1176
1177 return 0;
1178 }
1179
1180 //-----------------------------------------------------------------------------------------------------------
1181 void GetHealingAnimHitzones(EBandagingAnimationBodyParts eBandagingAnimBodyParts, out notnull array<HitZone> GroupHitZones)
1182 {
1183 array<HitZone> allGroupedHitZones = {};
1184 GetAllHitZones(allGroupedHitZones);
1185 SCR_CharacterHitZone charHitZone;
1186
1187 foreach (HitZone hitZone : allGroupedHitZones)
1188 {
1189 charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1190 if (charHitZone && charHitZone.GetBodyPartToHeal() == eBandagingAnimBodyParts)
1191 GroupHitZones.Insert(hitZone);
1192 }
1193 }
1194
1195 //-----------------------------------------------------------------------------------------------------------
1197 {
1198 float totalGroupHealth;
1199 float hitZoneQuantity;
1200
1201 array<HitZone> allGroupedHitZones = {};
1202 GetAllHitZones(allGroupedHitZones);
1203 SCR_CharacterHitZone charHitZone;
1204
1205 foreach (HitZone hitZone : allGroupedHitZones)
1206 {
1207 charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1208 if (!charHitZone || charHitZone.GetHitZoneGroup() != hitZoneGroup)
1209 continue;
1210
1211 totalGroupHealth += charHitZone.GetHealthScaled();
1212 hitZoneQuantity ++;
1213 }
1214
1215 if (hitZoneQuantity <= 0)
1216 return 0;
1217
1218 return totalGroupHealth / hitZoneQuantity;
1219 }
1220
1221 //-----------------------------------------------------------------------------------------------------------
1222 override float GetGroupDamageOverTime(ECharacterHitZoneGroup hitZoneGroup, EDamageType damageType)
1223 {
1224 float totalGroupDOT;
1225 array<HitZone> groupHitZones = {};
1226 GetHitZonesOfGroupFromOwner(hitZoneGroup, groupHitZones);
1227 DotDamageEffect dotEffect;
1228
1229 array<ref PersistentDamageEffect> effects = {};
1230 foreach (HitZone hitZone : groupHitZones)
1231 {
1233 if (hz)
1234 totalGroupDOT += hz.GetHitZoneDamageOverTime(damageType);
1235 }
1236
1237 return totalGroupDOT;
1238 }
1239
1240 //-----------------------------------------------------------------------------------------------------------
1242 {
1243 return m_aTourniquettedGroups && m_aTourniquettedGroups.Contains(hitZoneGroup);
1244 }
1245
1246 //------------------------------------------------------------------------------------------------
1247 void SetTourniquettedGroup(ECharacterHitZoneGroup hitZoneGroup, bool setTourniquetted)
1248 {
1249 if (setTourniquetted)
1250 {
1253 else if (m_aTourniquettedGroups.Contains(hitZoneGroup))
1254 return;
1255
1256 m_aTourniquettedGroups.Insert(hitZoneGroup);
1257 }
1258 else
1259 {
1260 if (!m_aTourniquettedGroups || !m_aTourniquettedGroups.Contains(hitZoneGroup))
1261 return;
1262 else
1263 m_aTourniquettedGroups.RemoveItem(hitZoneGroup);
1264
1265 if (m_aTourniquettedGroups.IsEmpty())
1267 }
1268
1269 UpdateCharacterGroupDamage(hitZoneGroup);
1270 }
1271
1272 //-----------------------------------------------------------------------------------------------------------
1274 {
1275 return m_aBeingHealedGroup && m_aBeingHealedGroup.Contains(hitZoneGroup);
1276 }
1277
1278 //------------------------------------------------------------------------------------------------
1279 void SetGroupIsBeingHealed(ECharacterHitZoneGroup hitZoneGroup, bool setIsBeingHealed)
1280 {
1281 if (setIsBeingHealed)
1282 {
1285 else if (m_aBeingHealedGroup.Contains(hitZoneGroup))
1286 return;
1287
1288 m_aBeingHealedGroup.Insert(hitZoneGroup);
1289 }
1290 else
1291 {
1292 if (!m_aBeingHealedGroup || !m_aBeingHealedGroup.Contains(hitZoneGroup))
1293 return;
1294 else
1295 m_aBeingHealedGroup.RemoveItem(hitZoneGroup);
1296
1297 if (m_aBeingHealedGroup.IsEmpty())
1298 m_aBeingHealedGroup = null;
1299 }
1300 }
1301
1302 //------------------------------------------------------------------------------------------------
1304 {
1305 return m_aSalineBaggedGroups && m_aSalineBaggedGroups.Contains(hitZoneGroup);
1306 }
1307
1308 //------------------------------------------------------------------------------------------------
1311 void SetSalineBaggedGroup(ECharacterHitZoneGroup hitZoneGroup, bool setSalineBagged)
1312 {
1313 if (setSalineBagged)
1314 {
1317 else if (m_aSalineBaggedGroups.Contains(hitZoneGroup))
1318 return;
1319
1320 m_aSalineBaggedGroups.Insert(hitZoneGroup);
1321 }
1322 else
1323 {
1324 if (!m_aSalineBaggedGroups || !m_aSalineBaggedGroups.Contains(hitZoneGroup))
1325 return;
1326 else
1327 m_aSalineBaggedGroups.RemoveItem(hitZoneGroup);
1328
1329 if (m_aSalineBaggedGroups.IsEmpty())
1330 m_aSalineBaggedGroups = null;
1331 }
1332 }
1333
1334 //------------------------------------------------------------------------------------------------
1337 {
1338 array<ref SCR_PersistentDamageEffect> effects = {};
1339 GetPersistentEffects(effects);
1340 foreach (SCR_PersistentDamageEffect effect : effects)
1341 {
1342 if (effect.GetDamageType() == EDamageType.BLEEDING)
1343 return true;
1344 }
1345
1346 return false;
1347 }
1348
1349 //------------------------------------------------------------------------------------------------
1350 HitZone GetMostDOTHitZone(EDamageType damageType, bool includeVirtualHZs = false, array<EHitZoneGroup> allowedGroups = null)
1351 {
1352 float highestDOT;
1353 float localDOT;
1354 HitZone highestDOTHitZone;
1355 array<HitZone> hitZones = {};
1356
1357 if (includeVirtualHZs)
1358 GetAllHitZones(hitZones);
1359 else
1360 GetPhysicalHitZones(hitZones);
1361
1362 foreach (HitZone hitZone : hitZones)
1363 {
1364 if (allowedGroups)
1365 {
1366 SCR_CharacterHitZone charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1367 if (!charHitZone)
1368 continue;
1369
1370 if (!allowedGroups.Contains(charHitZone.GetHitZoneGroup()))
1371 continue;
1372 }
1373
1374 DotDamageEffect dotEffect;
1375 array<ref SCR_PersistentDamageEffect> damageEffects = {};
1376 FindDamageEffectsOnHitZone(hitZone, damageEffects);
1377
1378 foreach (SCR_PersistentDamageEffect effect : damageEffects)
1379 {
1380 if (damageType != effect.GetDamageType())
1381 continue;
1382
1383 dotEffect = DotDamageEffect.Cast(effect);
1384 if (!dotEffect)
1385 continue;
1386
1387 localDOT += dotEffect.GetDPS();
1388 }
1389
1390 if (localDOT > highestDOT)
1391 {
1392 highestDOT = localDOT;
1393 highestDOTHitZone = hitZone;
1394 }
1395 }
1396 return highestDOTHitZone;
1397 }
1398
1399 //-----------------------------------------------------------------------------------------------------------
1400 static void GetAllLimbs(notnull out array<ECharacterHitZoneGroup> limbs)
1401 {
1402 limbs.Copy(LIMB_GROUPS);
1403 }
1404
1405 //-----------------------------------------------------------------------------------------------------------
1406 static void GetAllExtremities(notnull out array<ECharacterHitZoneGroup> limbs)
1407 {
1408 limbs.Copy(EXTREMITY_LIMB_GROUPS);
1409 }
1410
1411 //-----------------------------------------------------------------------------------------------------------
1413 {
1414 const float TOURNIQUETTED_LEG_DAMAGE = 0.7;
1415 const float TOURNIQUETTED_ARMS_DAMAGE = 0.7;
1416 // Updated OnDamageStateChanged, movement damage is equal to the accumulative damage of all hitZones in the limb divided by the amount of limbs of type
1417 if (hitZoneGroup == ECharacterHitZoneGroup.LEFTLEG || hitZoneGroup == ECharacterHitZoneGroup.RIGHTLEG)
1418 {
1419 float leftLegDamage;
1421 leftLegDamage = TOURNIQUETTED_LEG_DAMAGE;
1422 else
1423 leftLegDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.LEFTLEG));
1424
1425 float rightLegDamage;
1427 rightLegDamage = TOURNIQUETTED_LEG_DAMAGE;
1428 else
1429 rightLegDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.RIGHTLEG));
1430
1431 float legDamage = (leftLegDamage + rightLegDamage) * 0.5 * m_fMovementDamageMultiplier;
1432 SetMovementDamage(Math.Clamp(legDamage, 0, m_fMaxMovementDamage));
1433 return;
1434 }
1435
1436 if (hitZoneGroup == ECharacterHitZoneGroup.LEFTARM || hitZoneGroup == ECharacterHitZoneGroup.RIGHTARM)
1437 {
1438 float leftArmDamage;
1440 leftArmDamage = TOURNIQUETTED_ARMS_DAMAGE;
1441 else
1442 leftArmDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.LEFTARM));
1443
1444 float rightArmDamage;
1446 rightArmDamage = TOURNIQUETTED_ARMS_DAMAGE;
1447 else
1448 rightArmDamage = (1 - GetGroupHealthScaled(ECharacterHitZoneGroup.RIGHTARM));
1449
1450 float armDamage = (leftArmDamage + rightArmDamage) * 0.5 * m_fAimingDamageMultiplier;
1451 SetAimingDamage(armDamage);
1452 }
1453 }
1454
1455 //-----------------------------------------------------------------------------------------------------------
1461 ECharacterHitZoneGroup GetCharMostDOTHitzoneGroup(EDamageType damageType, bool onlyExtremities = false, bool ignoreTQdHitZones = false, bool ignoreIfBeingTreated = false)
1462 {
1464 return 0;
1465
1466 array<float> DOTValues = {};
1467 typename groupEnum = ECharacterHitZoneGroup;
1468 int groupCount = groupEnum.GetVariableCount();
1469
1470 for (int i; i < groupCount; i++)
1471 DOTValues.Insert(0);
1472
1473 float highestDOT;
1474 array<HitZone> hitZones = {};
1475
1476 GetPhysicalHitZones(hitZones);
1477 SCR_CharacterHitZone charHitZone;
1479
1480 foreach (HitZone hitZone : hitZones)
1481 {
1482 charHitZone = SCR_CharacterHitZone.Cast(hitZone);
1483 if (!charHitZone)
1484 continue;
1485
1486 // Virtual hitZones don't count to the accumulation of bleedings
1487 group = charHitZone.GetHitZoneGroup();
1488 if (group == EHitZoneGroup.VIRTUAL)
1489 continue;
1490
1491 // If only extremities are desired for checking, continue if group is not an extremity
1492 if (onlyExtremities && !EXTREMITY_LIMB_GROUPS.Contains(group))
1493 continue;
1494
1495 float DOT = charHitZone.GetHitZoneDamageOverTime(EDamageType.BLEEDING);
1496 if (DOT == 0)
1497 continue;
1498
1499 // GetHitZoneBleedingDPS() is unaware of tourniquet status, so it is reduced seperately here.
1500 if (GetGroupTourniquetted(group))
1501 {
1503 if (ignoreTQdHitZones)
1504 {
1505 DOTValues[LIMB_GROUPS.Find(group)] = 0;
1506 continue;
1507 }
1508 }
1509
1510 // if desired, bleedingHitzones that are being treated are skipped so another hitZone will be healed by this inquiry
1511 if (GetGroupIsBeingHealed(group))
1512 {
1513 if (ignoreIfBeingTreated)
1514 {
1515 DOTValues[LIMB_GROUPS.Find(group)] = 0;
1516 continue;
1517 }
1518 }
1519
1520 DOTValues[LIMB_GROUPS.Find(group)] = DOTValues[LIMB_GROUPS.Find(group)] + DOT;
1521 }
1522
1523 ECharacterHitZoneGroup mostDOTHitZoneGroup;
1524
1525 for (int i; i < groupCount; i++)
1526 {
1527 if (DOTValues[i] > highestDOT)
1528 {
1529 highestDOT = DOTValues[i];
1530 mostDOTHitZoneGroup = i;
1531 }
1532 }
1533
1534 return LIMB_GROUPS.Get(mostDOTHitZoneGroup);
1535 }
1536
1537 //-----------------------------------------------------------------------------------------------------------
1540 {
1541 switch (hzGroup)
1542 {
1543 case ECharacterHitZoneGroup.HEAD:
1544 {
1545 return "Head";
1546 } break;
1547
1548 case ECharacterHitZoneGroup.UPPERTORSO:
1549 {
1550 return "Spine5";
1551 } break;
1552
1553 case ECharacterHitZoneGroup.LOWERTORSO:
1554 {
1555 return "Spine1";
1556 } break;
1557
1558 case ECharacterHitZoneGroup.LEFTARM:
1559 {
1560 return "LeftForeArm";
1561 } break;
1562
1563 case ECharacterHitZoneGroup.RIGHTARM:
1564 {
1565 return "RightForeArm";
1566 } break;
1567
1568 case ECharacterHitZoneGroup.LEFTLEG:
1569 {
1570 return "LeftKnee";
1571 } break;
1572
1573 case ECharacterHitZoneGroup.RIGHTLEG:
1574 {
1575 return "RightKnee";
1576 }
1577 }
1578
1579 return string.Empty;
1580 }
1581
1582 //------------------------------------------------------------------------------------------------
1583 override void OnPostInit(IEntity owner)
1584 {
1585 super.OnPostInit(owner);
1586
1587 SCR_BaseGameMode baseGameMode = SCR_BaseGameMode.Cast(GetGame().GetGameMode());
1588 if (baseGameMode)
1590
1591 LIMB_GROUPS = {};
1593
1594 ChimeraCharacter character = ChimeraCharacter.Cast(owner);
1595 if (!character)
1596 return;
1597
1599 if (controller)
1600 controller.m_OnLifeStateChanged.Insert(OnLifeStateChanged);
1601
1602 RplComponent rpl = RplComponent.Cast(owner.FindComponent(RplComponent));
1603 if (rpl)
1604 {
1605 auto hz = GetDefaultHitZone();
1606 if (hz && !hz.IsProxy())
1607 {
1608 SCR_CharacterBuoyancyComponent charBuoyancyComp = SCR_CharacterBuoyancyComponent.Cast(owner.FindComponent(SCR_CharacterBuoyancyComponent));
1609 if (charBuoyancyComp)
1610 charBuoyancyComp.GetOnWaterEnter().Insert(OnWaterEnter);
1611 }
1612 }
1613
1615
1616 // CallLater because GetOwner().GetPhysics() returns empty until GC finishes onPostInit
1617 Physics physics = owner.GetPhysics();
1618 GetGame().GetCallqueue().CallLater(SetExactMinImpulse, param1:owner);
1619
1620#ifdef ENABLE_DIAG
1621 DiagMenu.RegisterBool(SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE,"","Log player damage","GameCode");
1622
1623 if (System.IsCLIParam("logPlayerDamage"))
1624 DiagMenu.SetValue(SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE, true);
1625#endif
1626 }
1627
1628 //------------------------------------------------------------------------------------------------
1629 void RegenPhysicalHitZones(bool skipRegenDelay = false)
1630 {
1632
1633 regen.SetInstigator(Instigator.CreateInstigator(GetOwner()));
1634 regen.SetAffectedHitZone(GetDefaultHitZone());
1635 regen.SetDPS(m_fRegenScale);
1636 regen.SetDamageType(EDamageType.REGENERATION);
1637 regen.SkipRegenDelay(skipRegenDelay);
1638 AddDamageEffect(regen);
1639 }
1640
1641 //------------------------------------------------------------------------------------------------
1642 void RegenVirtualHitZone(SCR_RegeneratingHitZone targetHitZone, float dps = -1, bool skipRegenDelay = false)
1643 {
1644 if (!targetHitZone)
1645 return;
1646
1647 if (IsDamageEffectPresentOnHitZones(SCR_PassiveHitZoneRegenDamageEffect, {targetHitZone}))
1648 return;
1649
1651
1652 regen.SetInstigator(Instigator.CreateInstigator(GetOwner()));
1653 regen.SetAffectedHitZone(targetHitZone);
1654 regen.SetDPS(dps);
1655 regen.SetDamageType(EDamageType.REGENERATION);
1656 regen.SkipRegenDelay(skipRegenDelay);
1657 AddDamageEffect(regen);
1658 }
1659
1660 //------------------------------------------------------------------------------------------------
1661 protected void SetExactMinImpulse(IEntity owner)
1662 {
1663 if (!owner)
1664 return;
1665
1666 m_fMinImpulse = 80;
1667 Physics physics = owner.GetPhysics();
1668 if (physics)
1669 m_fMinImpulse = physics.GetMass();
1670 }
1671
1672 //------------------------------------------------------------------------------------------------
1673 protected void OnWaterEnter()
1674 {
1675 Physics physics = GetOwner().GetPhysics();
1676 if (!physics)
1677 return;
1678
1679 // Don't try to apply waterFallDamage when going less than X m/s. Only consider vertical speed
1680 vector totalVelocity = physics.GetVelocity();
1681 if (Math.AbsFloat(totalVelocity[1]) < m_fMinWaterFallDamageVelocity)
1682 return;
1683
1684 vector ownerTransform[4];
1685 physics.GetDirectWorldTransform(ownerTransform);
1686
1687 m_fHighestContact = totalVelocity.Length() * m_fWaterFallDamageMultiplier;
1688 CalculateCollisionDamage(GetOwner(), null, ownerTransform[3], m_fHighestContact, false);
1689 }
1690
1691 //------------------------------------------------------------------------------------------------
1693 override bool FilterContact(IEntity owner, IEntity other, Contact contact)
1694 {
1695 if (!IsFallingVehicle(owner, other, contact) && (Math.AbsFloat(contact.GetRelativeNormalVelocityBefore()) <= 3))
1696 return false;
1697
1698 // Do not recompute collisiondamage within the impulseDelay if it's lower than the highest one thus far
1699 float relativeNormalVelocityBefore = Math.AbsFloat(contact.GetRelativeNormalVelocityBefore());
1700 if (m_fHighestContact > relativeNormalVelocityBefore)
1701 return false;
1702
1703 if (!super.FilterContact(owner, other, contact))
1704 return false;
1705
1706 Physics otherPhys = other.GetPhysics();
1707 if (!otherPhys)
1708 return false;
1709
1710 if (!otherPhys.IsDynamic())
1711 return false;//if player just run into a wall then lets not make his life worse
1712
1713 float otherMomentum = contact.VelocityBefore2.Length() * otherPhys.GetMass();
1714 if (otherMomentum < MIN_OTHER_MOMENTUM)
1715 return false;//dont harm the player just for running into things
1716
1717 Physics ownerPhys = owner.GetPhysics();
1718 if (!ownerPhys)
1719 return false;
1720
1721 vector otherPositionBeforeContact = contact.Position - (contact.VelocityBefore2.Normalized());
1722 vector ownerPositionBeforeContact = contact.Position - (contact.VelocityBefore1.Normalized());
1723
1724 vector otherDirectionToCollisionPoint = vector.Direction(otherPositionBeforeContact, contact.Position).Normalized();
1725 vector ownerDirectionToCollisionPoint = vector.Direction(ownerPositionBeforeContact, contact.Position).Normalized();
1726
1727 float otherDot = vector.Dot(contact.VelocityBefore2.Normalized(), otherDirectionToCollisionPoint);
1728 if (otherDot > 0.7)
1729 otherDot = 1;//close enough to being direct frontal impact, thus consider it as such
1730
1731 otherMomentum *= otherDot;
1732
1733 float ownerDot = vector.Dot(contact.VelocityBefore1.Normalized(), ownerDirectionToCollisionPoint);
1734 float ownerMomentum = contact.VelocityBefore1.Length() * ownerPhys.GetMass() * ownerDot;
1735
1736 float directionalMomentum = otherMomentum + ownerMomentum;
1737
1738 //if momentum was not directed at the collision then it shouldnt matter, but this isnt perfect!
1739 //Because if you would have a 'M' shaped object moving right, then other object which would happen to be inside of it, would not receive damage because of this
1740 if (directionalMomentum < MIN_OTHER_MOMENTUM)
1741 return false;
1742
1743 m_fHighestContact = relativeNormalVelocityBefore;
1744
1745 int remainingTime = GetGame().GetCallqueue().GetRemainingTime(ResetContact);
1746 if (remainingTime == -1)
1747 {
1748 GetGame().GetCallqueue().CallLater(ResetContact, MINIMUM_IMPULSE_RESET_TIME, false);
1749 }
1750 else
1751 {
1752 GetGame().GetCallqueue().Remove(ResetContact);
1753 GetGame().GetCallqueue().CallLater(ResetContact, MINIMUM_IMPULSE_RESET_TIME, false);
1754 }
1755
1756 return true;
1757 }
1758
1759 //------------------------------------------------------------------------------------------------
1760 protected void ResetContact()
1761 {
1763 GetGame().GetCallqueue().Remove(ResetContact);
1764 }
1765
1766 //------------------------------------------------------------------------------------------------
1771 override protected void OnFilteredContact(IEntity owner, IEntity other, Contact contact)
1772 {
1773 float relativeNormalVelocityBefore = Math.AbsFloat(contact.GetRelativeNormalVelocityBefore());
1774
1775 if (IsFallingVehicle(owner, other, contact))
1776 {
1777 ApplyCollisionDamage(other, contact.Position, m_fVehicleFallDamage);
1778 m_fHighestContact = 0.0;
1779 return;
1780 }
1781 CalculateCollisionDamage(owner, other, contact.Position, relativeNormalVelocityBefore);
1782 }
1783
1784 //------------------------------------------------------------------------------------------------
1785 //
1786 protected void CalculateCollisionDamage(IEntity owner, IEntity other, vector collisionPosition, float highestCachedImpulse = 0, bool instantUnconsciousness = true)
1787 {
1788 HitZone defaultHitZone = GetDefaultHitZone();
1789 if (defaultHitZone.GetDamageState() == EDamageState.DESTROYED)
1790 return;
1791
1792 if (instantUnconsciousness)
1793 ForceUnconsciousness(GetResilienceHitZone().GetDamageStateThreshold(ECharacterDamageState.STATE3));
1794
1795 float momentumCharacterThreshold = m_fMinImpulse * 1 * Physics.KMH2MS;
1796 float momentumCharacterDestroy = m_fMinImpulse * 100 * Physics.KMH2MS;
1797 float damageScaleToCharacter = (momentumCharacterDestroy - momentumCharacterThreshold) * 0.0001;
1798
1799 float impactMomentum = Math.AbsFloat(m_fMinImpulse * highestCachedImpulse);
1800
1801 float damageValue = damageScaleToCharacter * (impactMomentum - momentumCharacterThreshold);
1802 if (damageValue <= 0)
1803 return;
1804
1805 // We apply the collisiondamage only every 200 ms at most.
1806 // 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.
1807 const int impulseDelay = 200;
1808 int remainingTime = GetGame().GetCallqueue().GetRemainingTime(ApplyCollisionDamage);
1809 if (remainingTime == -1)
1810 {
1811 GetGame().GetCallqueue().CallLater(ApplyCollisionDamage, impulseDelay, false, other, collisionPosition, damageValue);
1812 }
1813 else
1814 {
1815 GetGame().GetCallqueue().Remove(ApplyCollisionDamage);
1816 GetGame().GetCallqueue().CallLater(ApplyCollisionDamage, remainingTime, false, other, collisionPosition, damageValue);
1817 }
1818 }
1819
1820 //------------------------------------------------------------------------------------------------
1821 override void OnHandleFallDamage(EFallDamageType fallDamageType, vector velocityVector)
1822 {
1823 HitZone defaultHitZone = GetDefaultHitZone();
1824 if (defaultHitZone.GetDamageState() == EDamageState.DESTROYED)
1825 return;
1826
1828
1829 float damage = Math.InverseLerp(prefabData.GetFallDamageLimitNoDamage(), prefabData.GetFallDamageLimitFullDamage(), velocityVector.Length());
1830 if (damage <= 0)
1831 return;
1832
1833 if (fallDamageType == EFallDamageType.RAGDOLL)
1835
1836 if (fallDamageType == EFallDamageType.LANDING)
1837 HandleAnimatedFallDamage(GetDefaultHitZone().GetMaxHealth() * Math.Pow(damage, 3));
1838
1839 if (m_CommunicationSound && GetDefaultHitZone().GetDamageState() == EDamageState.DESTROYED)
1840 m_CommunicationSound.SoundEventDeath(false);
1841 }
1842
1843 //------------------------------------------------------------------------------------------------
1844 void HandleAnimatedFallDamage(float damage)
1845 {
1846 array<HitZone> targetHitZones = {};
1848
1849 if (targetHitZones.IsEmpty())
1850 return;
1851
1852 const float overDamageCutOff = 50;
1853 if (damage > overDamageCutOff)
1854 GetHitZonesOfGroupFromOwner(ECharacterHitZoneGroup.LOWERTORSO, targetHitZones, false);
1855
1856 damage *= 2;
1857 vector hitPosDirNorm[3];
1858 SCR_DamageContext context = new SCR_DamageContext(EDamageType.COLLISION, damage/targetHitZones.Count(), hitPosDirNorm, GetOwner(), null, Instigator.CreateInstigator(GetOwner()), null, -1, -1);
1859 context.damageEffect = new SCR_AnimatedFallDamageEffect();
1860
1861 foreach (HitZone hitZone : targetHitZones)
1862 {
1863 context.struckHitZone = hitZone;
1864 HandleDamage(context);
1865 }
1866
1867 // Give character impulse when dying from animated falldamage to make the ragdoll transition more seamless and smooth
1868 SCR_CharacterAnimationComponent animComp = SCR_CharacterAnimationComponent.Cast(GetOwner().FindComponent(SCR_CharacterAnimationComponent));
1869 if (animComp)
1870 {
1871 float randomDirectional = Math.RandomFloat(-0.5, 0.3);
1872 animComp.AddRagdollEffectorDamage(Vector(randomDirectional, 1, 0), Vector(randomDirectional, -0.5, 0.3).Normalized(), 82, 15, 2);
1873 }
1874 }
1875
1876 //------------------------------------------------------------------------------------------------
1877 void HandleRagdollFallDamage(notnull HitZone contactingHitZone, float damage)
1878 {
1879 array<int> colliderIDs = {};
1880 contactingHitZone.GetColliderIDs(colliderIDs);
1881 if (colliderIDs.IsEmpty())
1882 return;
1883
1884 vector colliderTransform[4];
1885 Physics physics = GetOwner().GetPhysics();
1886
1887 physics.GetGeomWorldTransform(colliderIDs[0], colliderTransform);
1888
1889 array<HitZone> targetHitZones = {};
1890 GetPhysicalHitZones(targetHitZones);
1891 GetNearestHitZones(colliderTransform[3], targetHitZones, 5);
1892
1893 if (targetHitZones.IsEmpty())
1894 return;
1895
1896 vector hitPosDirNorm[3];
1897 hitPosDirNorm[0] = colliderTransform[3];
1898
1899 SCR_DamageContext context = new SCR_DamageContext(EDamageType.COLLISION, (damage*100) / targetHitZones.Count(), hitPosDirNorm, GetOwner(), null, GetInstigator(), null, -1, -1);
1900 context.damageEffect = new SCR_RagdollFallDamageEffect();
1901
1902 foreach (HitZone hitZone : targetHitZones)
1903 {
1904 context.struckHitZone = hitZone;
1905 HandleDamage(context);
1906 }
1907 }
1908
1909 //------------------------------------------------------------------------------------------------
1910 protected void ApplyCollisionDamage(IEntity other, vector collisionPosition, float damageValue)
1911 {
1912 if (!GetOwner())
1913 return;
1914
1915 HitZone defaultHitZone = GetDefaultHitZone();
1916 if (defaultHitZone.GetDamageState() == EDamageState.DESTROYED)
1917 return;
1918
1919 array<HitZone> characterHitZones = {};
1920 GetAllHitZones(characterHitZones);
1921
1922 // Apply collisionDamage only to the 6 nearest hitzones to the contactpoint
1923 const int hitZonesReturnAmount = 6;
1924 GetNearestHitZones(collisionPosition, characterHitZones, hitZonesReturnAmount);
1925
1926 Vehicle vehicle = Vehicle.Cast(other);
1927 if (vehicle && vehicle.GetPilot())
1928 other = vehicle.GetPilot();
1929
1930 vector hitPosDirNorm[3];
1931 hitPosDirNorm[0] = collisionPosition;
1932
1933 SCR_DamageContext context = new SCR_DamageContext(EDamageType.COLLISION, damageValue/hitZonesReturnAmount, hitPosDirNorm, GetOwner(), characterHitZones[0], Instigator.CreateInstigator(other), null, -1, -1);
1934
1935 foreach (HitZone characterHitZone : characterHitZones)
1936 {
1937 if (characterHitZone.GetDamageState() == EDamageState.DESTROYED)
1938 continue;
1939
1940 context.struckHitZone = characterHitZone;
1941 HandleDamage(context);
1942 }
1943 }
1944
1945 //------------------------------------------------------------------------------------------------
1948 void PlaySoundEvent(string soundEventName)
1949 {
1950 if (soundEventName.IsEmpty())
1951 return;
1952
1954 if (!soundComp)
1955 return;
1956
1957 soundComp.SoundEvent(soundEventName);
1958 }
1959
1960 //------------------------------------------------------------------------------------------------
1965 void GetNearestHitZones(vector worldPosition, notnull inout array<HitZone> nearestHitZones, int hitZonesReturnAmount)
1966 {
1967 if (nearestHitZones.IsEmpty())
1968 return;
1969
1970 array<vector> colliderDistances = {};
1971 array<int> IDs = {};
1972 Physics physics = GetOwner().GetPhysics();
1973
1974 vector colliderTransform[4];
1975 vector relativePosition;
1976 vector colliderDistance;
1977
1978 foreach (HitZone hitZone : nearestHitZones)
1979 {
1980 if (!hitZone.HasColliderNodes())
1981 continue;
1982
1983 IDs.Clear();
1984 hitZone.GetColliderIDs(IDs);
1985 foreach (int ID : IDs)
1986 {
1987 physics.GetGeomWorldTransform(ID, colliderTransform);
1988 relativePosition = worldPosition - colliderTransform[3];
1989 colliderDistance = {relativePosition.LengthSq(), ID, 0};
1990 colliderDistances.Insert(colliderDistance);
1991 }
1992 }
1993
1994 // Order colliderDistances by distance from close to furthest
1995 colliderDistances.Sort();
1996
1997 array<int> closestIDs = {};
1998 for (int i; i < hitZonesReturnAmount; i++)
1999 {
2000 closestIDs.InsertAt(colliderDistances[i][1], i);
2001 }
2002
2003 nearestHitZones.Clear();
2004 GetHitZonesByColliderIDs(nearestHitZones, closestIDs);
2005 }
2006
2007 //------------------------------------------------------------------------------------------------
2009 protected override bool ShouldOverrideInstigator(notnull Instigator currentInstigator, notnull Instigator newInstigator)
2010 {
2011 //If the new instigator is self and owner is unconscious, don't override it. Instigator remains cause of unconsciousness.
2012 if (newInstigator.GetInstigatorEntity() == GetOwner())
2013 {
2014 ChimeraCharacter character = ChimeraCharacter.Cast(GetOwner());
2015 if (character && character.GetCharacterController().GetLifeState() == ECharacterLifeState.INCAPACITATED)
2016 return false;
2017 }
2018
2019 return super.ShouldOverrideInstigator(currentInstigator, newInstigator);
2020 }
2021
2022 //------------------------------------------------------------------------------------------------
2024 protected override void OnDamageStateChanged(EDamageState newState, EDamageState previousDamageState, bool isJIP)
2025 {
2026 super.OnDamageStateChanged(newState, previousDamageState, isJIP);
2027
2028 if (newState == EDamageState.UNDAMAGED || newState == EDamageState.DESTROYED)
2030 }
2031
2032 //-----------------------------------------------------------------------------------------------------------
2033 protected override void OnDamage(notnull BaseDamageContext damageContext)
2034 {
2035 super.OnDamage(damageContext);
2036
2037 if (damageContext.damageValue > 0)
2039
2040 KnockOffTheHelmet(damageContext);
2041 #ifdef ENABLE_DIAG
2042 if (DiagMenu.GetBool(SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE))
2043 {
2044 SCR_HitZone scriptedHz = SCR_HitZone.Cast(damageContext.struckHitZone);
2045 if (!scriptedHz)
2046 return;
2047
2048 IEntity hzOwner = scriptedHz.GetOwner();
2049 if (!hzOwner)
2050 return;
2051
2052 string instigatorName;
2053 int instigatorID = damageContext.instigator.GetInstigatorPlayerID();
2054 IEntity instigatorEntity = damageContext.instigator.GetInstigatorEntity();
2055
2056 if (instigatorID > 0)
2057 {
2058 instigatorName = GetGame().GetPlayerManager().GetPlayerName(instigatorID);
2059 }
2060 else
2061 {
2062 ResourceName prefabName;
2063 if (instigatorEntity)
2064 {
2065 EntityPrefabData prefabData = instigatorEntity.GetPrefabData();
2066 if (prefabData)
2067 prefabName = prefabData.GetPrefabName();
2068 }
2069
2070 if (prefabName.IsEmpty())
2071 {
2072 if (instigatorEntity)
2073 {
2074 instigatorName = ((instigatorEntity.GetID()).ToString());
2075 }
2076 else
2077 {
2078 instigatorName = instigatorEntity.ToString();
2079 }
2080 }
2081 else
2082 {
2083 TStringArray strs = new TStringArray;
2084 prefabName.Split("/", strs, true);
2085 instigatorName = ((instigatorEntity.GetID()).ToString()) + strs[strs.Count() - 1];
2086 }
2087 }
2088
2089 string hzOwnerName;
2090 int hzOwnerID = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(hzOwner);
2091 if (hzOwnerID > 0)
2092 {
2093 hzOwnerName = GetGame().GetPlayerManager().GetPlayerName(hzOwnerID);
2094 }
2095 else
2096 {
2097 EntityPrefabData prefabData = hzOwner.GetPrefabData();
2098 ResourceName prefabName = prefabData.GetPrefabName();
2099
2100 if (prefabName.IsEmpty())
2101 {
2102 hzOwnerName = ((hzOwner.GetID()).ToString());
2103 }
2104 else
2105 {
2106 TStringArray strs = new TStringArray;
2107 prefabName.Split("/", strs, true);
2108 hzOwnerName = ((hzOwner.GetID()).ToString()) + strs[strs.Count() - 1];
2109 }
2110 }
2111
2112 if (EntityUtils.IsPlayer(instigatorEntity) || EntityUtils.IsPlayer(hzOwner))
2113 PrintFormat("HIT LOG: (%1) damaged (%2) - [Damage = %3, Speed = %4]", instigatorName, hzOwnerName, damageContext.damageValue, damageContext.impactVelocity);
2114 }
2115 #endif
2116 }
2117
2118 #ifdef ENABLE_DIAG
2119
2120 //------------------------------------------------------------------------------------------------
2121 override void DiagOnlyIfPossessedByPlayerController(notnull IEntity owner)
2122 {
2123 ProcessDebug(owner);
2124 }
2125
2126 //------------------------------------------------------------------------------------------------
2127 void ProcessDebug(IEntity owner)
2128 {
2129 DbgUI.Text("See: GameCode >> Hit Zones >> Player HitZones for damages");
2130 DbgUI.Text("Applying damage from client is NOT possible");
2131 DbgUI.Text("Only conventional DOT damage are shown in debug");
2132 DbgUI.Text("Y to damage every hitZone for 4 damage");
2133 DbgUI.Text("U to reduce chest health by 10");
2134 DbgUI.Text("I to reduce right thigh health by 10");
2135 DbgUI.Text("O to reduce left thigh health by 10");
2136 DbgUI.Text("P to add bleeding to right thigh");
2137 DbgUI.Text("T to add bleeding to left thigh");
2138 DbgUI.Text("K to reset damage effect");
2139
2140 vector hitPosDirNorm[3];
2141
2142 if (Debug.KeyState(KeyCode.KC_Y))
2143 {
2144 Debug.ClearKey(KeyCode.KC_Y);
2145 AddParticularBleeding("RThigh", intensityFloat: Math.RandomFloat(0.9, 0.99));
2146 AddParticularBleeding("LThigh", intensityFloat: Math.RandomFloat(0.9, 0.99));
2147 AddParticularBleeding("Chest", intensityFloat: Math.RandomFloat(0.9, 0.99));
2148 AddParticularBleeding("Head", intensityFloat: Math.RandomFloat(0.9, 0.99));
2149 AddParticularBleeding("LArm", intensityFloat: Math.RandomFloat(0.9, 0.99));
2150 AddParticularBleeding("Abdomen", intensityFloat: Math.RandomFloat(0.9, 0.99));
2151 AddParticularBleeding("RArm", intensityFloat: Math.RandomFloat(0.9, 0.99));
2152 }
2153 if (Debug.KeyState(KeyCode.KC_U))
2154 {
2155 Debug.ClearKey(KeyCode.KC_U);
2156 Print(HealHitZones(10, false, true));
2157 }
2158 if (Debug.KeyState(KeyCode.KC_I))
2159 {
2160 Debug.ClearKey(KeyCode.KC_I);
2161
2162 SCR_DamageContext damageContext = new SCR_DamageContext(EDamageType.TRUE, 10, hitPosDirNorm, owner, GetHitZoneByName("RThigh"), Instigator.CreateInstigator(owner), null, -1, -1);
2163 HandleDamage(damageContext);
2164 }
2165 if (Debug.KeyState(KeyCode.KC_O))
2166 {
2167 Debug.ClearKey(KeyCode.KC_O);
2168
2169 SCR_DamageContext damageContext = new SCR_DamageContext(EDamageType.TRUE, 10, hitPosDirNorm, owner, GetHitZoneByName("LThigh"), Instigator.CreateInstigator(owner), null, -1, -1);
2170 HandleDamage(damageContext);
2171 }
2172 if (Debug.KeyState(KeyCode.KC_P))
2173 {
2174 Debug.ClearKey(KeyCode.KC_P);
2175 AddParticularBleeding("RThigh");
2176 }
2177 if (Debug.KeyState(KeyCode.KC_T))
2178 {
2179 Debug.ClearKey(KeyCode.KC_T);
2180 AddParticularBleeding("LThigh");
2181 }
2182 if (Debug.KeyState(KeyCode.KC_K))
2183 {
2184 Debug.ClearKey(KeyCode.KC_K);
2185 FullHeal();
2186 }
2187 }
2188#endif
2189};
SCR_DebugMenuID
This enum contains all IDs for DiagMenu entries added in script.
Definition DebugMenuID.c:4
EHitZoneGroup
ArmaReforgerScripted GetGame()
Definition game.c:1398
params precision
SCR_BaseGameMode GetGameMode()
override bool HandleDamage(BaseDamageContext damageContext, IEntity owner)
EBandagingAnimationBodyParts
ECharacterBloodState
ECharacterDamageState
ECharacterResilienceState
SCR_CharacterSoundComponentClass GetComponentData()
int GetHitZonesOfGroupsFromOwner(notnull array< EHitZoneGroup > hitZoneGroups, out notnull array< HitZone > groupHitZones)
int GetHitZonesOfGroupFromOwner(EHitZoneGroup hitZoneGroup, out notnull array< HitZone > groupHitZones, bool clearArray=true)
void GetPhysicalHitZones(out notnull array< HitZone > physicalHitZones)
Return hit zones with colliders assigned.
void Kill(notnull Instigator instigator)
float HealHitZones(float healthToDistribute, bool sequential=false, float maxHealThresholdScaled=1, array< HitZone > alternativeHitZones=null)
float GetMaxHealth()
EDamageType type
void ProcessDebug()
void ParticleEffectEntity(IEntitySource src, IEntity parent)
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
enum EVehicleType IEntity
Diagnostic and developer menu system.
Definition DiagMenu.c:18
proto external Managed FindComponent(typename typeName)
proto external Physics GetPhysics()
proto external EntityID GetID()
proto external EntityPrefabData GetPrefabData()
Definition Math.c:13
SCR_GameModeHealthSettings GetGameModeHealthSettings()
Blood - does not receive damage directly, only via scripted events.
ref OnLifeStateChangedInvoker m_OnLifeStateChanged
float GetGroupHealthScaled(ECharacterHitZoneGroup hitZoneGroup)
override void OnHandleFallDamage(EFallDamageType fallDamageType, vector velocityVector)
bool GetPermitUnconsciousness()
Returns whether unconsciousness is permitted. Character prefab has highest authority,...
void HandleRagdollFallDamage(notnull HitZone contactingHitZone, float damage)
ref map< SCR_CharacterHitZone, ref SCR_ArmoredClothItemData > m_mClothItemDataMap
void KnockOffTheHelmet(notnull BaseDamageContext damageContext)
override GameMaterial OverrideHitMaterial(HitZone struckHitzone)
static const ref array< ECharacterHitZoneGroup > EXTREMITY_LIMB_GROUPS
ref array< ECharacterHitZoneGroup > m_aTourniquettedGroups
override bool FilterContact(IEntity owner, IEntity other, Contact contact)
Filter out any contacts under a reasonable speed for damage.
void CalculateCollisionDamage(IEntity owner, IEntity other, vector collisionPosition, float highestCachedImpulse=0, bool instantUnconsciousness=true)
ECharacterHitZoneGroup GetCharMostDOTHitzoneGroup(EDamageType damageType, bool onlyExtremities=false, bool ignoreTQdHitZones=false, bool ignoreIfBeingTreated=false)
ref array< ECharacterHitZoneGroup > m_aSalineBaggedGroups
void ApplyCollisionDamage(IEntity other, vector collisionPosition, float damageValue)
bool GetGroupIsBeingHealed(ECharacterHitZoneGroup hitZoneGroup)
static ref array< ECharacterHitZoneGroup > LIMB_GROUPS
void SetGroupIsBeingHealed(ECharacterHitZoneGroup hitZoneGroup, bool setIsBeingHealed)
bool GetGroupSalineBagged(ECharacterHitZoneGroup hitZoneGroup)
static void GetAllExtremities(notnull out array< ECharacterHitZoneGroup > limbs)
void RemoveArmorData(notnull SCR_CharacterHitZone charHitZone)
Remove armor data from clothing item prefab from map, delete map if empty.
SCR_CharacterHitZone GetRandomPhysicalHitZone(bool includeBleedingHZs=false)
Get a random physical hitZone.
void RegenVirtualHitZone(SCR_RegeneratingHitZone targetHitZone, float dps=-1, bool skipRegenDelay=false)
void ArmorHitEventEffects(float damage)
Function called from SCR_ArmorDamageManagerComponent in case player is shot in armor....
float GetArmorProtection(notnull SCR_CharacterHitZone charHitZone, EDamageType damageType)
Get protection value of particular hitZone stored on m_mClothItemDataMap.
void ArmorHitEventDamage(EDamageType type, float damage, IEntity instigator)
Function called from SCR_ArmorDamageManagerComponent in case player is shot in armor....
void CreateBleedingParticleEffect(notnull HitZone hitZone, int colliderDescriptorIndex)
Called to start bleeding effect on a specificied bone.
override void FullHeal(bool ignoreHealingDOT=true)
bool IsFallingVehicle(IEntity owner, IEntity other, Contact contact)
override void OnDamage(notnull BaseDamageContext damageContext)
bool IsIndefinitelyUnconscious(bool onlyPlayers=true)
Returns whether this character will remain unconscious with no chance to wake up without intervention...
string GetBoneName(ECharacterHitZoneGroup hzGroup)
Get bone name for given hz group.
void SoundHit(bool critical, EDamageType damageType)
override void OnDamageEffectRemoved(notnull SCR_DamageEffect dmgEffect)
HitZone GetMostDOTHitZone(EDamageType damageType, bool includeVirtualHZs=false, array< EHitZoneGroup > allowedGroups=null)
void AddRandomBleeding()
Add bleeding to a random physical hitZone.
bool GetGroupTourniquetted(ECharacterHitZoneGroup hitZoneGroup)
void RemoveGroupBleeding(ECharacterHitZoneGroup charHZGroup)
Terminate all bleeding effects on every hitZone of a group.
void InsertArmorData(notnull SCR_CharacterHitZone charHitZone, SCR_ArmoredClothItemData attributes)
Insert armor data from clothing item prefab into map, create map if null.
bool ShouldBeUnconscious()
Check whether character health state meets requirements for consciousness.
void RemoveAllBleedingParticles()
Remove all bleeding particle effects from all bleeding hitZones.
void GetHealingAnimHitzones(EBandagingAnimationBodyParts eBandagingAnimBodyParts, out notnull array< HitZone > GroupHitZones)
bool IsBleeding()
the official and globally used way of checking if bleeding tests for any PERSISTENT damage effects of...
void OnLifeStateChanged(ECharacterLifeState previousLifeState, ECharacterLifeState newLifeState, bool isJIP)
void AddSpecialContactEffect(notnull SCR_SpecialCollisionDamageEffect effect)
void SoundDeath(int previousLifestate)
Tell m_CommunicationSound that this character is now dead.
void SetTourniquettedGroup(ECharacterHitZoneGroup hitZoneGroup, bool setTourniquetted)
void AddBleedingToArray(notnull HitZone hitZone)
Register hitzone in array as bleeding.
void UpdateCharacterGroupDamage(ECharacterHitZoneGroup hitZoneGroup)
void SetSalineBaggedGroup(ECharacterHitZoneGroup hitZoneGroup, bool setSalineBagged)
void UpdateArmorDataMap(notnull SCR_ArmoredClothItemData armorAttr, bool remove)
If !remove, take data from prefab and insert to map as class. If remove, remove this hitZone's stored...
override void OnDamageStateChanged(EDamageState newState, EDamageState previousDamageState, bool isJIP)
Invoked when damage state changes.
void RemoveAllBleedingParticlesAfterDeath()
Clean up blood particle sources after death.
void GetNearestHitZones(vector worldPosition, notnull inout array< HitZone > nearestHitZones, int hitZonesReturnAmount)
SCR_ArmoredClothItemData GetArmorData(notnull SCR_CharacterHitZone charHitZone)
Get data from m_mClothItemDataMap.
EBandagingAnimationBodyParts FindAssociatedBandagingBodyPart(ECharacterHitZoneGroup hitZoneGroup)
void AddBleedingEffectOnHitZone(notnull SCR_CharacterHitZone hitZone, int colliderDescriptorIndex=-1)
override void OnDamageEffectAdded(notnull SCR_DamageEffect dmgEffect)
void OnFilteredContact(IEntity owner, IEntity other, Contact contact)
ECharacterHitZoneGroup FindAssociatedHitZoneGroup(EBandagingAnimationBodyParts bodyPartToBandage)
ref map< HitZone, ParticleEffectEntity > m_mBleedingParticles
void SoundKnockout()
Tell m_CommunicationSound to stop character from screaming, otherwise ignore.
override bool HijackDamageHandling(notnull BaseDamageContext damageContext)
Hijack collisiondamage in case it's applied to defaultHZ, since that means it's falldamage.
void AddParticularBleeding(string hitZoneName="Chest", ECharacterDamageState intensityEnum=ECharacterDamageState.WOUNDED, float intensityFloat=-1)
Add bleeding to a particular physical hitZone.
void AddBloodToClothes(notnull SCR_CharacterHitZone hitZone, float immediateBloodEffect)
static void GetAllLimbs(notnull out array< ECharacterHitZoneGroup > limbs)
bool SynchronizedSoundEvent(notnull SCR_PersistentDamageEffect broadcastingEffect, int effectId=-1)
override bool ShouldOverrideInstigator(notnull Instigator currentInstigator, notnull Instigator newInstigator)
Called whenever an instigator is going to be set.
override bool CanBeHealed(bool ignoreHealingDOT=true)
void RemoveBleedingFromArray(notnull HitZone hitZone)
Unregister hitzone from array.
override float GetGroupDamageOverTime(ECharacterHitZoneGroup hitZoneGroup, EDamageType damageType)
InventoryItemComponent GetItemFromLoadoutSlot(LoadoutAreaType eSlot)
void ApplyDamagePassRules(notnull BaseDamageContext damageContext)
IEntity GetOwner()
Definition SCR_HitZone.c:42
float GetHitZoneDamageOverTime(EDamageType targetDamageType)
Get current total damage per second of this hitZone.
Definition Types.c:486
enum EPhysicsLayerPresets Vehicle
Definition gameLib.c:24
IEntity GetOwner()
Owner entity of the fuel tank.
EHitReactionType
ECharacterLifeState
array< ref SCR_PersistentDamageEffect > GetAllPersistentEffectsOfType(typename effectTypeName, bool includeInheritedTypes=false)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
EFallDamageType
EDamageType
Definition EDamageType.c:13
EDamageState
KeyCode
Definition KeyCode.c:13
proto external EParticleEffectState GetState()
BaseProjectileComponentClass GameComponentClass GetInstigator()
void RplRpc(RplChannel channel, RplRcver rcver, RplCondition condition=RplCondition.None, string customConditionName="")
Definition EnNetwork.c:95
RplRcver
Definition RplRcver.c:59
RplChannel
Communication channel. Reliable is guaranteed to be delivered. Unreliable not.
Definition RplChannel.c:14
Tuple param1
array< string > TStringArray
Definition Types.c:385
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.
proto native vector Vector(float x, float y, float z)
void Debug()
Definition Types.c:327
EmitterParam
@ ID
Ordered by Group application ID.