21 static const int BLOOD_CLOTHES_UPDATE_PERIOD = 1000;
24 const float DEATH_BLEEDOUT_SCALE = 4;
27 protected float m_fHighestContact;
28 protected float m_fMinImpulse;
31 static ref array<ECharacterHitZoneGroup> LIMB_GROUPS;
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;
40 protected ref map<SCR_CharacterHitZone, ref SCR_ArmoredClothItemData> m_mClothItemDataMap;
42 protected ref array<HitZone> m_aBleedingHitZones;
43 protected ref map<HitZone, ParticleEffectEntity> m_mBleedingParticles;
44 protected SCR_CharacterResilienceHitZone m_pResilienceHitZone;
48 protected SCR_CommunicationSoundComponent m_CommunicationSound;
50 static protected SCR_GameModeHealthSettings s_HealthSettings;
52 protected bool m_bDOTScaleChangedByGM;
53 protected bool m_bRegenScaleChangedByGM;
54 protected bool m_bUnconsciousnessSettingsChangedByGM;
55 protected bool m_bOverrideCharacterMedical;
58 [
Attribute(defvalue:
"", uiwidget: UIWidgets.ResourceNamePicker,
desc:
"Bleeding particle effect",
params:
"ptc", precision: 3,
category:
"Bleeding")]
59 protected ResourceName m_sBleedingParticle;
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;
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;
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;
70 [
Attribute(
"0.3", UIWidgets.Auto,
"Resilience regeneration scale while unconscious\n[x * 100%]")]
71 protected float m_fUnconsciousRegenerationScale;
73 [
Attribute(defvalue:
"true", uiwidget: UIWidgets.CheckBox,
desc:
"Whether unconsciousness is allowed",
category:
"Unconsciousness")]
74 protected bool m_bPermitUnconsciousness;
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;
80 event override void OnInit(IEntity owner)
84 s_HealthSettings = baseGameMode.GetGameModeHealthSettings();
96 protected bool ShouldBeUnconscious()
98 HitZone bloodHZ = GetBloodHitZone();
103 if (bloodHZ.GetDamageStateThreshold(bloodState) <= bloodHZ.GetDamageStateThreshold(
ECharacterBloodState.UNCONSCIOUS))
106 HitZone resilienceHZ = GetResilienceHitZone();
111 ChimeraCharacter character = ChimeraCharacter.Cast(
GetOwner());
115 CharacterControllerComponent controller = character.GetCharacterController();
121 if (controller.IsUnconscious())
123 if (resilienceHZ.GetDamageStateThreshold(resilienceState) <= resilienceHZ.GetDamageStateThreshold(
ECharacterResilienceState.WEAKENED))
128 if (resilienceHZ.GetDamageStateThreshold(resilienceState) <= resilienceHZ.GetDamageStateThreshold(
ECharacterResilienceState.UNCONSCIOUS))
136 void UpdateConsciousness()
138 bool unconscious = ShouldBeUnconscious();
142 if (unconscious && (!GetPermitUnconsciousness() || (m_pBloodHitZone && m_pBloodHitZone.GetDamageState() ==
ECharacterBloodState.DESTROYED)))
148 ChimeraCharacter character = ChimeraCharacter.Cast(
GetOwner());
152 CharacterControllerComponent controller = character.GetCharacterController();
156 controller.SetUnconscious(unconscious);
161 void ForceUnconsciousness(
float resilienceHealth = 0)
163 if (!GetPermitUnconsciousness())
166 HitZone resilienceHZ = GetResilienceHitZone();
170 resilienceHZ.SetHealth(resilienceHealth);
171 UpdateConsciousness();
176 void InsertArmorData(notnull
SCR_CharacterHitZone charHitZone, SCR_ArmoredClothItemData attributes)
178 if (!m_mClothItemDataMap)
179 m_mClothItemDataMap =
new map<SCR_CharacterHitZone, ref SCR_ArmoredClothItemData>();
181 m_mClothItemDataMap.Insert(charHitZone, attributes);
188 if (!m_mClothItemDataMap)
191 if (m_mClothItemDataMap.Contains(charHitZone))
192 m_mClothItemDataMap.Remove(charHitZone);
194 if (m_mClothItemDataMap.IsEmpty())
195 m_mClothItemDataMap =
null;
200 void UpdateArmorDataMap(notnull SCR_ArmoredClothItemData armorAttr,
bool remove)
202 foreach (
string hitZoneName : armorAttr.m_aProtectedHitZones)
209 RemoveArmorData(hitZone);
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
221 InsertArmorData(hitZone, armoredClothItemData);
233 SCR_ArmoredClothItemData armorData = GetArmorData(charHitZone);
237 if (!armorData.m_Material)
240 return armorData.m_Material.GetResource().ToBaseContainer();
247 if (!m_mClothItemDataMap)
250 if (m_mClothItemDataMap.Contains(charHitZone))
251 return m_mClothItemDataMap.Get(charHitZone);
260 SCR_ArmoredClothItemData localArmorData = GetArmorData(charHitZone);
264 foreach(
EDamageType protectedType : localArmorData.m_aProtectedDamageTypes)
266 if (damageType == protectedType)
267 return localArmorData.m_eProtection;
275 void ArmorHitEventEffects(
float damage)
279 ChimeraCharacter character = ChimeraCharacter.Cast(
GetOwner());
283 CharacterControllerComponent controller = character.GetCharacterController();
295 void ArmorHitEventDamage(
EDamageType type,
float damage, IEntity instigator)
297 if (m_pResilienceHitZone)
298 m_pResilienceHitZone.HandleDamage(damage,
type, instigator);
305 super.OnDamageOverTimeAdded(dType, dps, hz);
323 regenHZ.RemovePassiveRegeneration();
330 super.OnDamageOverTimeRemoved(dType, hz);
341 regenHZ.ScheduleRegeneration();
348 super.OnDamageStateChanged(state);
352 if (IsRplReady() || !GetDefaultHitZone().
IsProxy())
355 RemoveAllBleedingParticlesAfterDeath();
360 void SoundHit(
bool critical,
EDamageType damageType)
366 if (m_CommunicationSound)
367 m_CommunicationSound.SoundEventHit(critical, damageType);
374 if (m_CommunicationSound && m_CommunicationSound.IsPlaying())
375 m_CommunicationSound.SoundEventDeath(
true);
385 if (m_pHeadHitZone && m_pHeadHitZone.GetDamageState() ==
EDamageState.DESTROYED)
392 if (m_CommunicationSound)
393 m_CommunicationSound.SoundEventDeath(silent);
400 m_CommunicationSound.DelayedSoundEventPriority(
SCR_SoundEvent.SOUND_VOICE_PAIN_RELIEVE, SCR_ECommunicationSoundEventPriority.SOUND_PAIN_RELIEVE, SCR_CommunicationSoundComponent.DEFAULT_EVENT_PRIORITY_DELAY);
405 void RemoveAllBleedingParticlesAfterDeath()
409 bleedingRate = DEATH_BLEEDOUT_SCALE * m_pBloodHitZone.GetDamageOverTime(
EDamageType.BLEEDING);
412 if (bleedingRate > 0)
413 delay = m_pBloodHitZone.GetHealth() / bleedingRate;
416 GetGame().GetCallqueue().Remove(RemoveAllBleedingParticles);
417 GetGame().GetCallqueue().CallLater(RemoveAllBleedingParticles, delay * 1000);
421 override void FullHeal(
bool ignoreHealingDOT =
true)
423 RemoveAllBleedings();
424 super.FullHeal(ignoreHealingDOT);
428 void UpdateBloodClothes()
432 GetGame().GetCallqueue().Remove(UpdateBloodClothes);
436 int bleedingHitZonesCount;
437 if (m_aBleedingHitZones)
438 bleedingHitZonesCount = m_aBleedingHitZones.Count();
440 if (bleedingHitZonesCount == 0)
442 GetGame().GetCallqueue().Remove(UpdateBloodClothes);
447 for (
int i; i < bleedingHitZonesCount; i++)
450 if (characterHitZone)
451 characterHitZone.AddBloodToClothes();
456 void SetBloodHitZone(
HitZone hitZone)
464 return m_pBloodHitZone;
469 float GetResilienceRegenScale()
471 if (!m_pBloodHitZone)
474 float criticalBloodLevel = m_pBloodHitZone.GetDamageStateThreshold(
ECharacterBloodState.UNCONSCIOUS);
475 if (criticalBloodLevel >= 1)
478 float currentBloodLevel = m_pBloodHitZone.GetDamageStateThreshold(m_pBloodHitZone.GetDamageState());
479 if (currentBloodLevel < criticalBloodLevel)
482 float resilienceRegenScale = (currentBloodLevel - criticalBloodLevel) / (1 - criticalBloodLevel);
485 resilienceRegenScale *= m_fUnconsciousRegenerationScale;
487 return resilienceRegenScale;
491 void GetBleedingHitZones(out notnull array<HitZone> hitZones)
493 if (m_aBleedingHitZones)
494 hitZones.Copy(m_aBleedingHitZones);
498 void SetDOTScale(
float rate,
bool changed)
501 m_bDOTScaleChangedByGM = changed;
507 if (!s_HealthSettings || m_bDOTScaleChangedByGM)
510 return s_HealthSettings.GetBleedingScale();
514 void SetRegenScale(
float rate,
bool changed)
517 m_bRegenScaleChangedByGM = changed;
522 float GetRegenScale()
524 if (!s_HealthSettings || m_bRegenScaleChangedByGM)
527 return s_HealthSettings.GetRegenScale();
532 bool GetPermitUnconsciousness()
537 if (!s_HealthSettings || m_bUnconsciousnessSettingsChangedByGM)
540 return s_HealthSettings.IsUnconsciousnessPermitted();
544 void SetPermitUnconsciousness(
bool permit,
bool changed)
547 m_bUnconsciousnessSettingsChangedByGM = changed;
553 ChimeraCharacter character = ChimeraCharacter.Cast(
GetOwner());
557 CharacterControllerComponent controller = character.GetCharacterController();
561 return controller.GetLifeState();
565 void SetOverrideCharacterMedical(
bool permit)
567 m_bOverrideCharacterMedical = permit;
571 bool GetOverrideCharacterMedical()
573 return m_bOverrideCharacterMedical;
577 void SetResilienceHitZone(
HitZone hitZone)
579 m_pResilienceHitZone = SCR_CharacterResilienceHitZone.Cast(hitZone);
585 return m_pResilienceHitZone;
589 void SetHeadHitZone(
HitZone hitZone)
597 return m_pHeadHitZone;
606 [
RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
607 void RpcDo_AddBloodToClothes(
int hitZoneIndex,
float immediateBloodEffect)
609 array<HitZone> hitZones = {};
610 GetAllHitZones(hitZones);
613 hitZone.AddBloodToClothes(immediateBloodEffect);
624 if (!m_aBleedingHitZones)
625 m_aBleedingHitZones = {};
627 if (!m_aBleedingHitZones.Contains(hitZone))
628 m_aBleedingHitZones.Insert(hitZone);
631 float hitZoneDamageMultiplier = hitZone.GetHealthScaled();
632 float bleedingRate = hitZone.GetMaxBleedingRate() - hitZone.GetMaxBleedingRate() * hitZoneDamageMultiplier;
634 GetGame().GetCallqueue().CallLater(UpdateBloodClothes, BLOOD_CLOTHES_UPDATE_PERIOD,
true);
636 if (colliderDescriptorIndex == -1)
637 colliderDescriptorIndex = Math.RandomInt(0, hitZone.GetNumColliderDescriptors() - 1);
639 CreateBleedingParticleEffect(hitZone, bleedingRate, colliderDescriptorIndex);
644 m_pBloodHitZone.AddBleedingHZToMap(hitZone, localBleedingHZParams);
645 UpdateBleedingHitZones();
649 if (hitZone.IsProxy())
652 array<HitZone> hitZones = {};
653 GetAllHitZones(hitZones);
655 int hitZoneIndex = hitZones.Find(hitZone);
656 if (hitZoneIndex >= 0)
657 Rpc(RpcDo_AddBleedingHitZone, hitZoneIndex, colliderDescriptorIndex, GetGroupBleedingRate(hitZone.GetHitZoneGroup()));
664 void RemoveBleedingHitZone(notnull
HitZone hitZone)
666 RemoveBleedingParticleEffect(hitZone);
667 if (m_aBleedingHitZones)
669 m_aBleedingHitZones.RemoveItem(hitZone);
670 if (m_aBleedingHitZones.IsEmpty())
671 m_aBleedingHitZones =
null;
675 if (characterHitZone && m_pBloodHitZone)
676 m_pBloodHitZone.RemoveBleedingHZFromMap(characterHitZone);
678 if (hitZone.IsProxy())
681 hitZone.SetDamageOverTime(
EDamageType.BLEEDING, 0);
682 UpdateBleedingHitZones();
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()));
692 void UpdateBleedingHitZones()
695 float bleedingDamage;
696 float totalBleedingDamage;
698 if (!m_pBloodHitZone.GetHitZoneDOTMap())
700 m_pBloodHitZone.SetDamageOverTime(
EDamageType.BLEEDING, 0);
701 m_aGroupBleedingRates =
null;
705 m_aGroupBleedingRates = {};
706 foreach(
int enumValue : LIMB_GROUPS)
708 m_aGroupBleedingRates.Insert(0);
713 if (!localBleedingHZParams)
719 if (localBleedingHZParams.m_fDamageOverTime != 0)
720 hitZone.SetDamageOverTime(
EDamageType.BLEEDING, 1e-5);
722 if (!GetGroupTourniquetted(hitZoneGroup))
723 bleedingDamage = localBleedingHZParams.m_fDamageOverTime;
727 int hitZoneGroupIndex = LIMB_GROUPS.Find(hitZoneGroup);
728 m_aGroupBleedingRates[hitZoneGroupIndex] = m_aGroupBleedingRates[hitZoneGroupIndex] + localBleedingHZParams.m_fDamageOverTime;
730 totalBleedingDamage += bleedingDamage;
733 m_pBloodHitZone.SetDamageOverTime(
EDamageType.BLEEDING, totalBleedingDamage);
743 [
RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
744 void RpcDo_AddBleedingHitZone(
int hitZoneIndex,
int colliderDescriptorIndex,
float groupBleedingRate)
746 array<HitZone> hitZones = {};
747 GetAllHitZones(hitZones);
752 AddBleedingHitZone(hitZone, colliderDescriptorIndex);
754 if (groupBleedingRate >= 0)
755 SetGroupBleedingRate(hitZone.GetHitZoneGroup(), groupBleedingRate);
763 [
RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
764 void RpcDo_RemoveBleedingHitZone(
int hitZoneIndex,
float groupBleedingRate)
766 array<HitZone> hitZones = {};
767 GetAllHitZones(hitZones);
772 RemoveBleedingHitZone(hitZone);
774 if (groupBleedingRate >= 0)
775 SetGroupBleedingRate(hitZone.GetHitZoneGroup(), groupBleedingRate);
780 void CreateBleedingParticleEffect(notnull
HitZone hitZone,
float bleedingRate,
int colliderDescriptorIndex)
782 if (System.IsConsoleApp())
786 if (m_sBleedingParticle.IsEmpty())
789 RemoveBleedingParticleEffect(hitZone);
791 if (bleedingRate == 0 || m_fBleedingParticleRateScale == 0)
796 if (characterHitZone.IsCovered())
803 if (!hitZone.TryGetColliderDescription(
GetOwner(), colliderDescriptorIndex, transform, boneIndex, boneNode))
807 ParticleEffectEntitySpawnParams spawnParams();
809 spawnParams.PivotID = boneNode;
810 ParticleEffectEntity particleEmitter = ParticleEffectEntity.SpawnParticleEffect(m_sBleedingParticle, spawnParams);
811 if (System.IsConsoleApp())
814 if (!particleEmitter)
816 Print(
"Particle emitter: " + particleEmitter.ToString() +
" There was a problem with creating the particle emitter: " + m_sBleedingParticle, LogLevel.WARNING);
821 if (!m_mBleedingParticles)
822 m_mBleedingParticles =
new map<HitZone, ParticleEffectEntity>;
824 m_mBleedingParticles.Insert(hitZone, particleEmitter);
827 Particles particles = particleEmitter.GetParticles();
829 particles.MultParam(-1, EmitterParam.BIRTH_RATE, bleedingRate * m_fBleedingParticleRateScale);
831 Print(
"Particle: " + particles.ToString() +
" Bleeding particle likely not created properly: " + m_sBleedingParticle, LogLevel.WARNING);
838 void RemoveBleedingParticleEffect(
HitZone hitZone)
840 if (!m_mBleedingParticles)
843 ParticleEffectEntity particleEmitter = m_mBleedingParticles.Get(hitZone);
846 particleEmitter.StopEmission();
847 m_mBleedingParticles.Remove(hitZone);
850 if (m_mBleedingParticles.IsEmpty())
851 m_mBleedingParticles =
null;
856 void RemoveAllBleedingParticles()
858 if (!m_aBleedingHitZones || m_aBleedingHitZones.IsEmpty())
861 foreach (
HitZone hitZone : m_aBleedingHitZones)
862 RemoveBleedingParticleEffect(hitZone);
867 void RemoveAllBleedings()
869 if (m_aBleedingHitZones)
872 for (
int i = m_aBleedingHitZones.Count() - 1; i >= 0; i--)
874 hitZone = m_aBleedingHitZones.Get(i);
876 RemoveBleedingHitZone(hitZone);
881 m_pBloodHitZone.SetDamageOverTime(
EDamageType.BLEEDING, 0);
888 if (!m_aBleedingHitZones)
894 array<HitZone> aBleedingHitZones = {};
895 aBleedingHitZones.Copy(m_aBleedingHitZones);
897 foreach (
HitZone hitZone : aBleedingHitZones)
900 if (!charHitZone || charHitZone.GetHitZoneGroup() != charHZGroup)
903 RemoveBleedingHitZone(hitZone);
913 if (!targetHitZone || targetHitZone.IsProxy())
921 if (intensityFloat < 0)
924 targetHitZone.SetHealthScaled(intensityFloat);
926 AddBleedingHitZone(targetHitZone);
931 void AddRandomBleeding()
934 array<HitZone> hitZones = {};
935 array<HitZone> validHitZones = {};
936 GetPhysicalHitZones(hitZones);
938 foreach (
HitZone hitZone: hitZones)
941 if (hitZone.GetDamageOverTime(
EDamageType.BLEEDING) > 0)
946 if (characterHitZone)
947 validHitZones.Insert(characterHitZone);
950 if (validHitZones.IsEmpty())
955 AddBleedingHitZone(hitZone);
961 array<HitZone> hitZones = {};
963 GetPhysicalHitZones(hitZones);
966 foreach (
HitZone hitZone : hitZones)
969 if (charHitZone && charHitZone.GetBodyPartToHeal() == bodyPartToBandage)
970 return charHitZone.GetHitZoneGroup();
979 array<HitZone> hitZones = {};
981 GetPhysicalHitZones(hitZones);
984 foreach (
HitZone hitZone : hitZones)
987 if (charHitZone && charHitZone.GetHitZoneGroup() == hitZoneGroup)
988 return charHitZone.GetBodyPartToHeal();
997 array<HitZone> allGroupedHitZones = {};
998 GetAllHitZones(allGroupedHitZones);
1001 foreach (
HitZone hitZone : allGroupedHitZones)
1004 if (charHitZone && charHitZone.GetBodyPartToHeal() == eBandagingAnimBodyParts)
1005 GroupHitZones.Insert(hitZone);
1012 float totalGroupHealth;
1013 float hitZoneQuantity;
1015 array<HitZone> allGroupedHitZones = {};
1016 GetAllHitZones(allGroupedHitZones);
1019 foreach (
HitZone hitZone : allGroupedHitZones)
1022 if (!charHitZone || charHitZone.GetHitZoneGroup() != hitZoneGroup)
1025 totalGroupHealth += charHitZone.GetHealthScaled();
1029 if (hitZoneQuantity <= 0)
1032 return totalGroupHealth / hitZoneQuantity;
1039 if (!m_aGroupBleedingRates)
1042 if (group < 0 && group >= m_aGroupBleedingRates.Count())
1045 return m_aGroupBleedingRates[LIMB_GROUPS.Find(group)];
1058 if (!m_aGroupBleedingRates && rate > 0)
1060 m_aGroupBleedingRates = {};
1061 foreach(
int enumValue : LIMB_GROUPS)
1063 m_aGroupBleedingRates.Insert(0);
1068 if (!m_aGroupBleedingRates || m_aGroupBleedingRates[LIMB_GROUPS.Find(group)] == rate)
1071 m_aGroupBleedingRates[LIMB_GROUPS.Find(group)] = rate;
1073 foreach (
float bleedingRate: m_aGroupBleedingRates)
1075 if (bleedingRate > 0)
1080 m_aGroupBleedingRates =
null;
1087 return GetGroupBleedingRate(hitZoneGroup);
1089 return super.GetGroupDamageOverTime(hitZoneGroup, damageType);
1095 return m_aTourniquettedGroups && m_aTourniquettedGroups.Contains(hitZoneGroup);
1101 if (setTourniquetted)
1103 if (!m_aTourniquettedGroups)
1104 m_aTourniquettedGroups = {};
1105 else if (m_aTourniquettedGroups.Contains(hitZoneGroup))
1108 m_aTourniquettedGroups.Insert(hitZoneGroup);
1112 if (!m_aTourniquettedGroups || !m_aTourniquettedGroups.Contains(hitZoneGroup))
1115 m_aTourniquettedGroups.RemoveItem(hitZoneGroup);
1117 if (m_aTourniquettedGroups.IsEmpty())
1118 m_aTourniquettedGroups =
null;
1121 UpdateBleedingHitZones();
1122 UpdateCharacterGroupDamage(hitZoneGroup);
1128 return m_aBeingHealedGroup && m_aBeingHealedGroup.Contains(hitZoneGroup);
1134 if (setIsBeingHealed)
1136 if (!m_aBeingHealedGroup)
1137 m_aBeingHealedGroup = {};
1138 else if (m_aBeingHealedGroup.Contains(hitZoneGroup))
1141 m_aBeingHealedGroup.Insert(hitZoneGroup);
1145 if (!m_aBeingHealedGroup || !m_aBeingHealedGroup.Contains(hitZoneGroup))
1148 m_aBeingHealedGroup.RemoveItem(hitZoneGroup);
1150 if (m_aBeingHealedGroup.IsEmpty())
1151 m_aBeingHealedGroup =
null;
1158 return m_aSalineBaggedGroups && m_aSalineBaggedGroups.Contains(hitZoneGroup);
1166 if (setSalineBagged)
1168 if (!m_aSalineBaggedGroups)
1169 m_aSalineBaggedGroups = {};
1170 else if (m_aSalineBaggedGroups.Contains(hitZoneGroup))
1173 m_aSalineBaggedGroups.Insert(hitZoneGroup);
1177 if (!m_aSalineBaggedGroups || !m_aSalineBaggedGroups.Contains(hitZoneGroup))
1180 m_aSalineBaggedGroups.RemoveItem(hitZoneGroup);
1182 if (m_aSalineBaggedGroups.IsEmpty())
1183 m_aSalineBaggedGroups =
null;
1188 HitZone GetMostDOTHitZone(
EDamageType damageType,
bool includeVirtualHZs =
false, array<EHitZoneGroup> allowedGroups =
null)
1193 array<HitZone> hitZones = {};
1195 if (includeVirtualHZs)
1196 GetAllHitZones(hitZones);
1198 GetPhysicalHitZones(hitZones);
1200 foreach (
HitZone hitZone : hitZones)
1208 if (!allowedGroups.Contains(charHitZone.GetHitZoneGroup()))
1212 localDOT = hitZone.GetDamageOverTime(damageType);
1213 if (localDOT > highestDOT)
1215 highestDOT = localDOT;
1216 highestDOTHitZone = hitZone;
1219 return highestDOTHitZone;
1223 static void GetAllLimbs(notnull out array<ECharacterHitZoneGroup> limbs)
1225 limbs.Copy(LIMB_GROUPS);
1229 static void GetAllExtremities(notnull out array<ECharacterHitZoneGroup> limbs)
1231 limbs.Copy(EXTREMITY_LIMB_GROUPS);
1237 float AIMING_DAMAGE_MULTIPLIER = 4;
1238 float TOURNIQUETTED_LEG_DAMAGE = 0.7;
1239 float TOURNIQUETTED_ARMS_DAMAGE = 0.7;
1243 float leftLegDamage;
1245 leftLegDamage = TOURNIQUETTED_LEG_DAMAGE;
1249 float rightLegDamage;
1251 rightLegDamage = TOURNIQUETTED_LEG_DAMAGE;
1255 float legDamage = (leftLegDamage + rightLegDamage) * 0.5;
1256 SetMovementDamage( legDamage );
1261 float leftArmDamage;
1263 leftArmDamage = TOURNIQUETTED_ARMS_DAMAGE;
1267 float rightArmDamage;
1269 rightArmDamage = TOURNIQUETTED_ARMS_DAMAGE;
1273 float armDamage = (leftArmDamage + rightArmDamage) * 0.5;
1274 SetAimingDamage(armDamage * AIMING_DAMAGE_MULTIPLIER);
1284 ECharacterHitZoneGroup GetCharMostDOTHitzoneGroup(
EDamageType damageType,
bool onlyExtremities =
false,
bool ignoreTQdHitZones =
false,
bool ignoreIfBeingTreated =
false)
1286 if (!m_aBleedingHitZones)
1289 array<float> DOTValues = {};
1291 int groupCount = groupEnum.GetVariableCount();
1293 for (
int i; i < groupCount; i++)
1294 DOTValues.Insert(0);
1298 array<HitZone> hitZones = {};
1300 GetPhysicalHitZones(hitZones);
1304 foreach (
HitZone hitZone : hitZones)
1311 group = charHitZone.GetHitZoneGroup();
1312 if (group == EHitZoneGroup.VIRTUAL)
1316 if (onlyExtremities && !EXTREMITY_LIMB_GROUPS.Contains(group))
1319 float DOT = m_pBloodHitZone.GetPhysicalHZBleedingDOT(charHitZone);
1324 if (GetGroupTourniquetted(group))
1327 if (ignoreTQdHitZones)
1329 DOTValues[LIMB_GROUPS.Find(group)] = 0;
1335 if (GetGroupIsBeingHealed(group))
1337 if (ignoreIfBeingTreated)
1339 DOTValues[LIMB_GROUPS.Find(group)] = 0;
1344 DOTValues[LIMB_GROUPS.Find(group)] = DOTValues[LIMB_GROUPS.Find(group)] + DOT;
1349 for (
int i; i < groupCount; i++)
1351 if (DOTValues[i] > highestDOT)
1353 highestDOT = DOTValues[i];
1354 mostDOTHitZoneGroup = i;
1358 return LIMB_GROUPS.Get(mostDOTHitZoneGroup);
1384 return "LeftForeArm";
1389 return "RightForeArm";
1403 return string.Empty;
1407 override void OnPostInit(IEntity owner)
1409 super.OnPostInit(owner);
1410 m_CommunicationSound = SCR_CommunicationSoundComponent.Cast(owner.FindComponent(SCR_CommunicationSoundComponent));
1412 RplComponent rpl = RplComponent.Cast(owner.FindComponent(RplComponent));
1413 if (!rpl || !rpl.IsProxy())
1415 Physics physics = owner.GetPhysics();
1417 GetGame().GetCallqueue().CallLater(SetExactMinImpulse, param1:owner);
1421 DiagMenu.RegisterBool(
SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE,
"",
"Log player damage",
"GameCode");
1423 if (System.IsCLIParam(
"logPlayerDamage"))
1424 DiagMenu.SetValue(
SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE,
true);
1429 protected void SetExactMinImpulse(IEntity owner)
1435 Physics physics = owner.GetPhysics();
1445 void ContactDamage(IEntity owner, IEntity other, Contact contact)
1447 if (Math.AbsFloat(contact.GetRelativeNormalVelocityBefore()) > 3)
1449 ComputeCollisionDamage(owner, other, contact);
1452 ForceUnconsciousness(0.3);
1457 protected void ComputeCollisionDamage(notnull IEntity owner, notnull IEntity other, notnull Contact contact)
1460 float relativeNormalVelocityBefore = Math.AbsFloat(contact.GetRelativeNormalVelocityBefore());
1461 if (m_fHighestContact > relativeNormalVelocityBefore)
1464 m_fHighestContact = relativeNormalVelocityBefore;
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;
1470 float impactMomentum = Math.AbsFloat(
m_fMinImpulse * relativeNormalVelocityBefore);
1472 float damageValue = damageScaleToCharacter * (impactMomentum - momentumCharacterThreshold);
1473 if (damageValue <= 0)
1478 int impulseDelay = 200;
1479 int remainingTime =
GetGame().GetCallqueue().GetRemainingTime(ApplyCollisionDamage);
1480 if (remainingTime == -1)
1482 GetGame().GetCallqueue().CallLater(ApplyCollisionDamage, impulseDelay,
false, contact, other, damageValue);
1486 GetGame().GetCallqueue().Remove(ApplyCollisionDamage);
1487 GetGame().GetCallqueue().CallLater(ApplyCollisionDamage, remainingTime,
false, contact, other, damageValue);
1492 protected void ApplyCollisionDamage(Contact contact, IEntity other,
float damageValue)
1497 array<HitZone> characterHitZones = {};
1498 GetAllHitZones(characterHitZones);
1501 int hitZonesReturnAmount = 6;
1502 GetNearestHitZones(contact.Position, characterHitZones, hitZonesReturnAmount);
1504 foreach (
HitZone characterHitZone : characterHitZones)
1506 if (characterHitZone.GetDamageState() ==
EDamageState.DESTROYED)
1509 characterHitZone.HandleDamage(damageValue / hitZonesReturnAmount,
EDamageType.COLLISION, other);
1512 m_fHighestContact = 0;
1520 void GetNearestHitZones(vector worldPosition, notnull inout array<HitZone> nearestHitZones,
int hitZonesReturnAmount)
1522 array<vector> hitZonePositions = {};
1523 array<int> IDs = {};
1524 foreach (
HitZone hitZone : nearestHitZones)
1526 if (!hitZone.HasColliderNodes())
1530 hitZone.GetColliderIDs(IDs);
1531 foreach (
int ID : IDs)
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);
1542 hitZonePositions.Sort();
1544 array<int> closestIDs = {};
1545 for (
int i; i < hitZonesReturnAmount; i++)
1547 vector hitZonePosition = hitZonePositions[i];
1548 closestIDs.InsertAt(hitZonePosition[1], i);
1551 nearestHitZones.Clear();
1552 GetHitZonesByColliderIDs(nearestHitZones, closestIDs);
1556 void HandleFallDamage(
float damage)
1558 array<HitZone> targetHitZones = {};
1562 float overDamageCutOff = 50;
1563 if (damage > overDamageCutOff)
1568 foreach (
HitZone hitZone : targetHitZones)
1576 super.OnDamage(damageContext);
1578 if (DiagMenu.GetBool(
SCR_DebugMenuID.DEBUGUI_CHARACTER_LOG_PLAYER_DAMAGE))
1584 IEntity hzOwner = scriptedHz.GetOwner();
1588 string instigatorName;
1589 int instigatorID = damageContext.instigator.GetInstigatorPlayerID();
1590 IEntity instigatorEntity = damageContext.instigator.GetInstigatorEntity();
1592 if (instigatorID > 0)
1594 instigatorName =
GetGame().GetPlayerManager().GetPlayerName(instigatorID);
1598 ResourceName prefabName;
1599 if (instigatorEntity)
1601 EntityPrefabData prefabData = instigatorEntity.GetPrefabData();
1603 prefabName = prefabData.GetPrefabName();
1606 if (prefabName.IsEmpty())
1608 if (instigatorEntity)
1610 instigatorName = ((instigatorEntity.GetID()).ToString());
1614 instigatorName = instigatorEntity.ToString();
1619 TStringArray strs =
new TStringArray;
1620 prefabName.Split(
"/", strs,
true);
1621 instigatorName = ((instigatorEntity.GetID()).ToString()) + strs[strs.Count() - 1];
1626 int hzOwnerID =
GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(hzOwner);
1629 hzOwnerName =
GetGame().GetPlayerManager().GetPlayerName(hzOwnerID);
1633 EntityPrefabData prefabData = hzOwner.GetPrefabData();
1634 ResourceName prefabName = prefabData.GetPrefabName();
1636 if (prefabName.IsEmpty())
1638 hzOwnerName = ((hzOwner.GetID()).ToString());
1642 TStringArray strs =
new TStringArray;
1643 prefabName.Split(
"/", strs,
true);
1644 hzOwnerName = ((hzOwner.GetID()).ToString()) + strs[strs.Count() - 1];
1649 PrintFormat(
"HIT LOG: (%1) damaged (%2) - [Damage = %3, Speed = %4]", instigatorName, hzOwnerName, damageContext.damageValue, damageContext.impactVelocity);
1653 override void OnDelete(IEntity owner)
1655 DisconnectFromDiagSystem(owner);
1657 super.OnDelete(owner);
1661 void DiagInit(IEntity owner)
1663 ConnectToDiagSystem(owner);
1665 DiagMenu.RegisterBool(
SCR_DebugMenuID.DEBUGUI_CHARACTER_DAMAGE,
"",
"Deal damage debug",
"Character");
1670 override void OnDiag(IEntity owner,
float timeSlice)
1672 ProcessDebug(owner);
1676 void ProcessDebug(IEntity owner)
1681 IEntity playerEntity =
GetGame().GetPlayerController().GetControlledEntity();
1682 if (playerEntity != owner)
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");
1696 vector hitPosDirNorm[3];
1698 if (Debug.KeyState(KeyCode.KC_Y))
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));
1709 if (Debug.KeyState(KeyCode.KC_U))
1711 Debug.ClearKey(KeyCode.KC_U);
1712 Print(HealHitZones(10,
false,
true));
1714 if (Debug.KeyState(KeyCode.KC_I))
1716 Debug.ClearKey(KeyCode.KC_I);
1719 HandleDamage(damageContext);
1721 if (Debug.KeyState(KeyCode.KC_O))
1723 Debug.ClearKey(KeyCode.KC_O);
1726 HandleDamage(damageContext);
1728 if (Debug.KeyState(KeyCode.KC_P))
1730 Debug.ClearKey(KeyCode.KC_P);
1731 AddParticularBleeding(
"RThigh");
1733 if (Debug.KeyState(KeyCode.KC_T))
1735 Debug.ClearKey(KeyCode.KC_T);
1736 AddParticularBleeding(
"LThigh");
1738 if (Debug.KeyState(KeyCode.KC_K))
1740 Debug.ClearKey(KeyCode.KC_K);