Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_VehicleDamageManagerComponent.c
Go to the documentation of this file.
3 HULL = 10,
4 ENGINE = 20,
7 WHEELS = 50,
8 CARGO = 60,
10 TURRET = 80,
11 OPTICS = 90,
13 VIEWPORT = 100,
17 PROPELLER = 130,
18 INSTRUMENTS = 140,
19 CONTROLS = 150,
20 LIGHT = 160,
21}
22
23//#define VEHICLE_DAMAGE_DEBUG
24//#define VEHICLE_DEBUG_OTHER
25class SCR_VehicleDamageManagerComponentClass : SCR_DamageManagerComponentClass
26{
27 [Attribute("3.5", desc: "Max distance of hitzone, which should receive damage, from contact point.", category: "Collision Damage")]
28 protected float m_fMaxSharedDamageDistance;
29
30 [Attribute("0.7", desc: "Damage multiplier for collisions from this side", params: "0.01 1000 0.01", category: "Collision Damage")]
32
33 [Attribute("0.7", desc: "Damage multiplier for collisions from this side", params: "0.01 1000 0.01", category: "Collision Damage")]
35
36 [Attribute("1", desc: "Damage multiplier for collisions from this side", params: "0.01 1000 0.01", category: "Collision Damage")]
38
39 [Attribute("1.5", desc: "Damage multiplier for collisions from this side", params: "0.01 1000 0.01", category: "Collision Damage")]
41
42 [Attribute("1.5", desc: "Damage multiplier for collisions from this side", params: "0.01 1000 0.01", category: "Collision Damage")]
44
45 [Attribute("2", desc: "Damage multiplier for collisions from this side", params: "0.01 1000 0.01", category: "Collision Damage")]
47
48 [Attribute("30", desc: "Speed in km/h over which will occupants be dealt damage in collision", category: "Collision Damage")]
50
51 [Attribute("100", desc: "Speed in km/h over which will occupants die in collision", category: "Collision Damage")]
52 protected float m_fOccupantsSpeedDeath;
53
54 [Attribute("0 0 0", desc: "Position for frontal impact calculation", category: "Collision Damage", params: "inf inf 0 purpose=coords space=entity coordsVar=m_vFrontalImpact")]
56
57 [Attribute("0", desc: "Frontal impact damage needed to destroy this vehicle\nTakes into account current damage multipliers\nUse context menu on Damage Manager component to compute this value.\n[hp]", category: "Collision Damage")]
58 protected float m_fVehicleDestroyDamage;
59
60 [Attribute("15", desc: "Speed of collision that damages the vehicle\n[km/h]", category: "Collision Damage")]
61 protected float m_fVehicleDamageSpeedThreshold;
62
63 [Attribute("120", desc: "Speed of collision that destroys the vehicle\n[km/h]", category: "Collision Damage")]
64 protected float m_fVehicleSpeedDestroy;
65
66 [Attribute("200", desc: "Explosion damage value which, if exceeded, may cause passengers to be ejected from the vehicle", category: "Passenger Ejection")]
68
69 [Attribute("0.5", desc: "Chance victim is ejected when explosion damage is sufficient to pass the m_iMinExplosionEjectionDamageThreshold \nLess than 0 = fully random \n1 = always", params: "-1 1 0.01", category: "Passenger Ejection")]
71
72 [Attribute("70", desc: "Collision damage value which, if exceeded, may cause passengers to be ejected from the vehicle", category: "Passenger Ejection")]
74
75 [Attribute("0.5", desc: "Chance victim is ejected when collisiondamage is sufficient to pass the m_iMinCollisionEjectionDamageThreshold \nLess than 0 = fully random \n1 = always", params: "-1 1 0.01", category: "Passenger Ejection")]
77
78 protected float m_fDamageScaleToVehicle = float.MAX;
79 protected float m_fMomentumVehicleThreshold = float.MAX;
80 protected float m_fDamageScaleToCharacter = float.MAX;
81 protected float m_fMomentumOccupantsThreshold = float.MAX;
82
83 protected static const float APPROXIMATE_CHARACTER_LETHAL_DAMAGE = 150;
84
85 //------------------------------------------------------------------------------------------------
88 {
89 return m_fMaxSharedDamageDistance;
90 }
91
92 //------------------------------------------------------------------------------------------------
94 float GetVehicleMomentumThreshold(float ownerMass)
95 {
98
99 m_fMomentumVehicleThreshold = ownerMass * m_fVehicleDamageSpeedThreshold * Physics.KMH2MS;
100
102 }
103
104 //------------------------------------------------------------------------------------------------
106 float GetVehicleDamageScale(float ownerMass)
107 {
108 if (m_fDamageScaleToVehicle != float.MAX)
110
111 float momentumVehicleDestroy = ownerMass * m_fVehicleSpeedDestroy * Physics.KMH2MS - GetVehicleMomentumThreshold(ownerMass);
112 if (momentumVehicleDestroy == 0)
113 {
116 }
117
118 m_fDamageScaleToVehicle = m_fVehicleDestroyDamage / momentumVehicleDestroy;
119
121 }
122
123 //------------------------------------------------------------------------------------------------
125 float GetCharacterDamageScale(float ownerMass)
126 {
127 if (m_fDamageScaleToCharacter != float.MAX)
129
130 float momentumOccupantsDeath = ownerMass * m_fOccupantsSpeedDeath * Physics.KMH2MS - GetOccupantsMomentumThreshold(ownerMass);
131 if (momentumOccupantsDeath == 0)
132 {
135 }
136
137 m_fDamageScaleToCharacter = APPROXIMATE_CHARACTER_LETHAL_DAMAGE / momentumOccupantsDeath;
138
140 }
141
142 //------------------------------------------------------------------------------------------------
153
154 //------------------------------------------------------------------------------------------------
157 {
158 return m_fVehicleSpeedDestroy;
159 }
160
161 //------------------------------------------------------------------------------------------------
164 {
165 return m_fVehicleDamageSpeedThreshold;
166 }
167
168 //------------------------------------------------------------------------------------------------
171 {
172 return m_fFrontMultiplier;
173 }
174
175 //------------------------------------------------------------------------------------------------
178 {
179 return m_fBottomMultiplier;
180 }
181
182 //------------------------------------------------------------------------------------------------
185 {
186 return m_fRearMultiplier;
187 }
188
189 //------------------------------------------------------------------------------------------------
192 {
193 return m_fLeftMultiplier;
194 }
195
196 //------------------------------------------------------------------------------------------------
199 {
200 return m_fRightMultiplier;
201 }
202
203 //------------------------------------------------------------------------------------------------
206 {
207 return m_fTopMultiplier;
208 }
209
210 //------------------------------------------------------------------------------------------------
216
217 //------------------------------------------------------------------------------------------------
220 {
222 }
223
224 //------------------------------------------------------------------------------------------------
227 {
228 return m_vFrontalImpact;
229 }
230
231 //------------------------------------------------------------------------------------------------
237
238 //------------------------------------------------------------------------------------------------
244
245 //------------------------------------------------------------------------------------------------
251
252 //------------------------------------------------------------------------------------------------
258}
259
275
276class SCR_VehicleDamageManagerComponent : SCR_DamageManagerComponent
277{
278 protected const float MIN_IMPULSE_THRESHOLD = 5;
279 protected const float HITZONE_DAMAGE_VALUE_THRESHOLD = 0.01;
280 protected const int MIN_RESPONSE_INDEX = SCR_EPhysicsResponseIndex.TINY_MOMENTUM;
281 protected const int MAX_RESPONSE_INDEX = SCR_EPhysicsResponseIndex.HUGE_MOMENTUM;
282 protected const int CACHED_COLLISION_PROCESSING_DELAY = 50;
283
286
287 static ref map<SCR_EPhysicsResponseIndex, float> s_mResponseIndexMomentumMap = new map<SCR_EPhysicsResponseIndex, float>();
288
289 [Attribute(defvalue: "0.7", desc: "Engine efficiency at which it is considered to be malfunctioning\n[x * 100%]", category: "Vehicle Damage")]
290 protected float m_fEngineMalfunctioningThreshold;
291
292 [Attribute(defvalue: "VehicleFireState", desc: "Vehicle parts fire state signal name", category: "Secondary damage")]
294
295 [Attribute(defvalue: "FuelTankFireState", desc: "Fuel tank fire signal name", category: "Secondary damage")]
297
298 [Attribute(defvalue: "SuppliesFireState", desc: "Supplies fire signal name", category: "Secondary damage")]
300
301 [Attribute(defvalue: "0.02", desc: "Fuel tank fire damage rate\n[x * 100%]", params: "0 100 0.001", category: "Secondary damage")]
302 protected float m_fFuelTankFireDamageRate;
303
304 [Attribute(defvalue: "0.02", desc: "Supplies fire damage rate\n[x * 100%]", params: "0 100 0.001", category: "Secondary damage")]
305 protected float m_fSuppliesFireDamageRate;
306
307 [Attribute(defvalue: "1", desc: "Delay between secondary fire damage\n[s]", params: "0 10000 0.1", category: "Secondary damage")]
308 protected float m_fSecondaryFireDamageDelay;
309
310 protected float m_fMinImpulse;
311 protected SCR_BaseCompartmentManagerComponent m_CompartmentManager;
312
313 protected static ref ScriptInvokerInt s_OnVehicleDestroyed;
314 protected static ref ScriptInvoker s_OnVehicleDamageStateChanged;
315
317 protected ref array<HitZone> m_aVehicleHitZones = {};
320
321 protected float m_fEngineEfficiency = 1;
322 protected bool m_bEngineFunctional = true;
323
324 protected float m_fGearboxEfficiency = 1;
325 protected bool m_bGearboxFunctional = true;
326
327 // The vehicle damage manager needs to know about all the burning hitZones that it consists of
328 protected ref array<SCR_FlammableHitZone> m_aFlammableHitZones;
330
331 // Vehicle fire
333
334 // Fuel tanks fire
335 protected ParticleEffectEntity m_FuelTankFireParticle; // Burning fuel particle
336 protected FuelManagerComponent m_FuelManager;
338
339 // Supplies fire
340 protected ParticleEffectEntity m_SuppliesFireParticle; // Burning supplies particle
342
343 // Audio features
347
348 // Impact FX
350
351 [RplProp(onRplName: "OnVehicleFireStateChanged")]
352 SCR_ESecondaryExplosionScale m_eVehicleFireState;
353
354 [RplProp(onRplName: "OnFuelTankFireStateChanged")]
355 SCR_ESecondaryExplosionScale m_eFuelTankFireState;
356
357 [RplProp(onRplName: "OnSuppliesFireStateChanged")]
358 SCR_ESecondaryExplosionScale m_eSuppliesFireState;
359
360 [RplProp()]
362
363 [RplProp()]
365
366 [RplProp()]
368
369#ifdef VEHICLE_DAMAGE_DEBUG
370 protected ref array<ref Shape> m_aDebugShapes = {};
371#endif
372#ifdef VEHICLE_DEBUG_OTHER
373 protected ref array<ref Shape> m_aDebugShapes = {};
374#endif
375
376 protected ref array<ref SCR_CollisionDamageContainer> m_LastFrameCollisions;
377 protected ref array<ref SCR_CollisionDamageContainer> m_CurrentCollisions;
378#ifdef ENABLE_DIAG
379 protected ref SCR_DebugShapeManager m_DebugShape = new SCR_DebugShapeManager();
380 protected ref array<ref Tuple2<vector, bool>> m_aLocalCollisions = {};
381#endif
382
383#ifdef WORKBENCH
388 protected const float HACK_DAMAGE_CALCULATION_REDUCTION = 0.9;
389#endif
390
391 //------------------------------------------------------------------------------------------------
393 SCR_VehicleDamageManagerComponentClass GetPrefabData()
394 {
395 return SCR_VehicleDamageManagerComponentClass.Cast(GetComponentData(GetOwner()));
396 }
397
398 //------------------------------------------------------------------------------------------------
400 float GetTopMultiplier()
401 {
402 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
403 if (!prefabData)
404 return 2;
405
406 return prefabData.GetTopMultiplier();
407 }
408
409 //------------------------------------------------------------------------------------------------
411 float GetRightMultiplier()
412 {
413 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
414 if (!prefabData)
415 return 1.5;
416
417 return prefabData.GetRightMultiplier();
418 }
419
420 //------------------------------------------------------------------------------------------------
422 float GetLeftMultiplier()
423 {
424 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
425 if (!prefabData)
426 return 1.5;
427
428 return prefabData.GetLeftMultiplier();
429 }
430
431 //------------------------------------------------------------------------------------------------
433 float GetRearMultiplier()
434 {
435 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
436 if (!prefabData)
437 return 1;
438
439 return prefabData.GetRearMultiplier();
440 }
441
442 //------------------------------------------------------------------------------------------------
444 float GetBottomMultiplier()
445 {
446 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
447 if (!prefabData)
448 return 0.7;
449
450 return prefabData.GetBottomMultiplier();
451 }
452
453 //------------------------------------------------------------------------------------------------
455 float GetFrontMultiplier()
456 {
457 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
458 if (!prefabData)
459 return 0.7;
460
461 return prefabData.GetFrontMultiplier();
462 }
463
464 //------------------------------------------------------------------------------------------------
467 {
468 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
469 if (!prefabData)
470 return 1.5;
471
472 return prefabData.GetMaxSharedDamageDistance();
473 }
474
475 //------------------------------------------------------------------------------------------------
478 {
479 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
480 if (!prefabData)
481 return 30;
482
483 return prefabData.GetOccupantsDamageSpeedThreshold();
484 }
485
486 //------------------------------------------------------------------------------------------------
489 {
490 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
491 if (!prefabData)
492 return 80;
493
494 return prefabData.GetOccupantsSpeedDeath();
495 }
496
497 //------------------------------------------------------------------------------------------------
500 {
501 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
502 if (!prefabData)
503 return 70;
504
505 return prefabData.GetMinCollisionEjectionDamageThreshold();
506 }
507
508 //------------------------------------------------------------------------------------------------
511 {
512 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
513 if (!prefabData)
514 return 200;
515
516 return prefabData.GetMinExplosionEjectionDamageThreshold();
517 }
518
519 //------------------------------------------------------------------------------------------------
522 {
523 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
524 if (!prefabData)
525 return 0.5;
526
527 return prefabData.GetCollisionDamageEjectionChance();
528 }
529
530 //------------------------------------------------------------------------------------------------
533 {
534 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
535 if (!prefabData)
536 return 0.5;
537
538 return prefabData.GetExplosionDamageEjectionChance();
539 }
540
541 //------------------------------------------------------------------------------------------------
543 vector GetFrontalImpact()
544 {
545 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
546 if (!prefabData)
547 return "0 1 1";
548
549 return prefabData.GetFrontalImpact();
550 }
551
552 //------------------------------------------------------------------------------------------------
556 {
557 switch (side)
558 {
559 case SCR_EBoxSide.FRONT:
560 return GetFrontMultiplier();
561 case SCR_EBoxSide.REAR:
562 return GetRearMultiplier();
563 case SCR_EBoxSide.BOTTOM:
564 return GetBottomMultiplier();
565 case SCR_EBoxSide.TOP:
566 return GetTopMultiplier();
567 case SCR_EBoxSide.LEFT:
568 return GetLeftMultiplier();
569 case SCR_EBoxSide.RIGHT:
570 return GetRightMultiplier();
571
572 }
573
574 return 1;
575 }
576
577 //------------------------------------------------------------------------------------------------
578 override void OnPostInit(IEntity owner)
579 {
580 super.OnPostInit(owner);
581
582 Vehicle vehicle = Vehicle.Cast(owner);
583 if (!vehicle)
584 return;
585
586 m_CompartmentManager = SCR_BaseCompartmentManagerComponent.Cast(vehicle.FindComponent(SCR_BaseCompartmentManagerComponent));
589 m_FuelManager = FuelManagerComponent.Cast(owner.FindComponent(FuelManagerComponent));
592
593 // can happen when any physics or simulation property is invalid
594 if (!m_Simulation)
595 {
596 m_fMinImpulse = 0;
597 return;
598 }
599
601 {
605 }
606
607 SCR_VehicleBuoyancyComponent vehicleBuoyancy = SCR_VehicleBuoyancyComponent.Cast(vehicle.FindComponent(SCR_VehicleBuoyancyComponent));
608 if (vehicleBuoyancy)
609 vehicleBuoyancy.GetOnWaterEnter().Insert(OnWaterEnter);
610
612
613 RplComponent rpl = vehicle.GetRplComponent();
614 if (rpl && rpl.IsProxy())
615 return;
616
617 Physics physics = owner.GetPhysics();
618 if (physics)
619 m_fMinImpulse = physics.GetMass() * 2;
620
621 if (m_fMinImpulse > 0)
622 SetEventMask(owner, EntityEvent.CONTACT);
623 }
624
625 //------------------------------------------------------------------------------------------------
628 // Register simulation feature hit zone
629 void RegisterVehicleHitZone(notnull HitZone hitZone)
630 {
631 // Remove the hitzone if it was already registered
632 if (!m_aVehicleHitZones.Contains(hitZone))
633 m_aVehicleHitZones.Insert(hitZone);
634 }
635
636 //------------------------------------------------------------------------------------------------
639 // Register simulation feature hit zone
641 {
642 m_aVehicleHitZones.RemoveItem(hitZone);
643 }
644
645 //------------------------------------------------------------------------------------------------
649 {
650 int engineCount;
651 float engineEfficiency;
652 bool engineFunctional;
653
654 int gearboxCount;
655 float gearboxEfficiency;
656 bool gearboxFunctional;
657
658 SCR_EngineHitZone engineHitZone;
659 SCR_GearboxHitZone gearboxHitZone;
660
661 foreach (HitZone hitZone : m_aVehicleHitZones)
662 {
663 engineHitZone = SCR_EngineHitZone.Cast(hitZone);
664 if (engineHitZone)
665 {
666 engineCount++;
667 engineEfficiency += engineHitZone.GetEfficiency();
668
669 if (engineHitZone.GetDamageState() != EDamageState.DESTROYED)
670 engineFunctional = true;
671
672 continue;
673 }
674
675 gearboxHitZone = SCR_GearboxHitZone.Cast(hitZone);
676 if (gearboxHitZone)
677 {
678 gearboxCount++;
679 gearboxEfficiency += gearboxHitZone.GetEfficiency();
680
681 if (gearboxHitZone.GetDamageState() != EDamageState.DESTROYED)
682 gearboxFunctional = true;
683
684 continue;
685 }
686 }
687
688 if (engineCount > 0)
689 {
690 SetEngineFunctional(engineFunctional);
691 SetEngineEfficiency(engineEfficiency / engineCount);
692 }
693
694 if (gearboxCount > 0)
695 {
696 SetGearboxFunctional(gearboxFunctional);
697 SetGearboxEfficiency(gearboxEfficiency / gearboxCount);
698 }
699
701 }
702
703 //------------------------------------------------------------------------------------------------
706 {
707 float movementDamage;
708
710 movementDamage = 1;
711 else
712 movementDamage = 1 - (m_fGearboxEfficiency * m_fEngineEfficiency);
713
714 SetMovementDamage(movementDamage);
715
716 VehicleControllerComponent controller = VehicleControllerComponent.Cast(m_Controller);
717 if (controller)
718 controller.SetCanMove(movementDamage < 1);
719 }
720
721 //------------------------------------------------------------------------------------------------
724 {
725 return m_bEngineFunctional;
726 }
727
728 //------------------------------------------------------------------------------------------------
729 protected void SetEngineFunctional(bool functional)
730 {
731 m_bEngineFunctional = functional;
732
733 if (functional)
734 return;
735
736 VehicleControllerComponent controller = VehicleControllerComponent.Cast(m_Controller);
737 if (controller && controller.IsEngineOn())
738 controller.StopEngine(false);
739 }
740
741 //------------------------------------------------------------------------------------------------
744 {
745 return m_fEngineMalfunctioningThreshold;
746 }
747
748 //------------------------------------------------------------------------------------------------
751 {
752 return m_fEngineEfficiency;
753 }
754
755 //------------------------------------------------------------------------------------------------
756 protected void SetEngineEfficiency(float efficiency)
757 {
758 m_fEngineEfficiency = efficiency;
759
761 if (simulation && simulation.IsValid())
762 {
763 simulation.EngineSetPeakTorqueState(efficiency * simulation.EngineGetPeakTorque());
764 simulation.EngineSetPeakPowerState(efficiency * simulation.EngineGetPeakPower());
765 }
766 }
767
768 //------------------------------------------------------------------------------------------------
769 protected void SetGearboxFunctional(bool functional)
770 {
771 m_bGearboxFunctional = functional;
772
773 VehicleControllerComponent controller = VehicleControllerComponent.Cast(m_Controller);
774 if (controller)
775 controller.SetCanMove(functional);
776 }
777
778 //------------------------------------------------------------------------------------------------
781 {
783 }
784
785 //------------------------------------------------------------------------------------------------
788 {
790 }
791
792 //------------------------------------------------------------------------------------------------
793 protected void SetGearboxEfficiency(float efficiency)
794 {
795 m_fGearboxEfficiency = efficiency;
796
798 if (simulation && simulation.IsValid())
799 simulation.GearboxSetEfficiencyState(efficiency * simulation.GearboxGetEfficiency());
800 }
801
802 //------------------------------------------------------------------------------------------------
812 int GetSurroundingHitzones(vector position, notnull Physics physics, float maxDistance, EDamageType damageType, out notnull array<HitZone> outHitZones, out float hitZonesDistancePercentSum = 0, out map<HitZone, float> outHitZoneDistanceProcentageMap = null)
813 {
814 outHitZones.Clear();
815 array<HitZone> allHitZones = {};
816 int count = GetAllHitZonesInHierarchy(allHitZones);
817 if (count <= 0)
818 return 0;
819
820 outHitZoneDistanceProcentageMap = new map<HitZone, float>();
821 array<int> hitZoneColliderIDs = {};
822 float distancePercent;
823 float currentDistance;
824 float maxDistanceSq = maxDistance * maxDistance;
825 float minDistance;
826 HitZoneContainerComponent hitZoneContainer;
827 IEntity hitZoneParentEntity;
828 vector mins, maxs;
829 vector center;
830 // Find hit zones that qualify for this damage
831 foreach (HitZone hitZone : allHitZones)
832 {
833 minDistance = float.MAX;
834 if (hitZone.GetDamageState() == EDamageState.DESTROYED)
835 continue;
836
837 if (hitZone.GetBaseDamageMultiplier() == 0)
838 continue;
839
840 if (hitZone.GetDamageMultiplier(damageType) == 0)
841 continue;
842
843 if (hitZone.GetColliderIDs(hitZoneColliderIDs) == 0)
844 {
845 hitZoneContainer = hitZone.GetHitZoneContainer();
846 if (!hitZoneContainer || hitZoneContainer == this)
847 continue;
848
849 hitZoneParentEntity = hitZoneContainer.GetOwner();
850 hitZoneParentEntity.GetBounds(mins, maxs);
851
852 center = mins + (maxs - mins) * 0.5;
853 minDistance = vector.DistanceSq(position, hitZoneParentEntity.CoordToParent(center));
854 }
855 else
856 {
857 foreach (int id : hitZoneColliderIDs)
858 {
859 currentDistance = vector.DistanceSq(position, physics.GetGeomWorldPosition(id));
860
861 if (currentDistance < minDistance)
862 minDistance = currentDistance;
863 }
864 }
865
866 if (minDistance > maxDistanceSq)
867 continue;
868
869 minDistance = Math.Sqrt(minDistance);
870 distancePercent = 1 - minDistance / maxDistance;
871
872 hitZonesDistancePercentSum += distancePercent;
873 outHitZoneDistanceProcentageMap.Insert(hitZone, distancePercent);
874 outHitZones.Insert(hitZone);
875 }
876
877 return outHitZones.Count();
878 }
879
880 //------------------------------------------------------------------------------------------------
887 float DamageSurroundingHitzones(vector position, float damage, EDamageType damageType, bool dontApply = false)
888 {
889 Physics physics = GetOwner().GetPhysics();
890 if (!physics)
891 return damage;
892
893 array<HitZone> hitZones = {};
894 map<HitZone, float> hitZoneDistancePercentMap;
895 float hitZonesDistancePercentSum;
896 int hitZonesCount = GetSurroundingHitzones(position, physics, GetMaxSharedDamageDistance(), damageType, hitZones, hitZonesDistancePercentSum, hitZoneDistancePercentMap);
897 if (hitZonesCount <= 0)
898 return damage;
899
900#ifdef ENABLE_DIAG
901 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) > 0)
902 Print(" Damage to absorb = " + damage + " of type = " + SCR_Enum.GetEnumName(EDamageType, damageType), LogLevel.NORMAL);
903
904 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) > 1)
905 Print(" Number of hit zones to which damage will be passed: " + hitZonesCount, LogLevel.NORMAL);
906#endif
907 float leftoverDamage = damage;
908 float currentDamage;
909 float currentDamagePercent;
910 float hitZoneHealth;
911 float damageMultiplier;
912
913 DamageManagerComponent damageManager;
914 vector empty[3];
915 empty[0] = vector.Zero;
916 empty[1] = vector.Zero;
917 empty[2] = vector.Zero;
918 foreach (HitZone hitZone, float distancePercent : hitZoneDistancePercentMap)
919 {
920 currentDamagePercent = distancePercent / hitZonesDistancePercentSum;
921 currentDamage = damage * currentDamagePercent;
922
923 hitZoneHealth = hitZone.GetHealth();
924
925#ifdef ENABLE_DIAG
926 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) > 1)
927 Print(" hit zone name = " + hitZone.GetName(), LogLevel.NORMAL);
928
929 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) > 2)
930 {
931 Print(" hit zone health = " + hitZoneHealth, LogLevel.NORMAL);
932 Print(" distance percent from the impact point = " + distancePercent, LogLevel.NORMAL);
933 Print(" percentage of the vehicle damage that can be transfered to this hit zone = " + currentDamagePercent + " = " + distancePercent + " / " + hitZonesDistancePercentSum, LogLevel.NORMAL);
934 Print(" damage that can be done to this hit zone = " + currentDamage + " = " + damage + " * " + currentDamagePercent, LogLevel.NORMAL);
935 }
936#endif
937
938 damageMultiplier = hitZone.GetDamageMultiplier(damageType) * hitZone.GetBaseDamageMultiplier();
939 if (damageMultiplier != 0)
940 {
941#ifdef ENABLE_DIAG
942 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) > 2)
943 Print(" clamped damage = " + (Math.Clamp(currentDamage, 0, (hitZoneHealth + hitZone.GetDamageReduction()) / damageMultiplier)) + " = Clamp(" + currentDamage + ", 0, (" + hitZoneHealth + " + " + hitZone.GetDamageReduction() + ") / " + damageMultiplier + ")", LogLevel.NORMAL);
944#endif
945 currentDamage = Math.Clamp(currentDamage, 0, (hitZoneHealth + hitZone.GetDamageReduction()) / damageMultiplier);
946 }
947
948 if (float.AlmostEqual(currentDamage, 0, HITZONE_DAMAGE_VALUE_THRESHOLD))
949 continue;
950
951 damageManager = DamageManagerComponent.Cast(hitZone.GetHitZoneContainer());
952 if (!damageManager)
953 continue;
954
955 SCR_DamageContext damageContext = new SCR_DamageContext(damageType, currentDamage, empty, GetOwner(), hitZone, null, null, -1, -1);
956 if (!dontApply)
957 damageManager.HandleDamage(damageContext);
958
959 leftoverDamage -= currentDamage;
960#ifdef ENABLE_DIAG
961 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) > 1)
962 {
963 Print(" damage done to the hit zone = " + currentDamage, LogLevel.NORMAL);
964 Print(" -----------------------------", LogLevel.NORMAL);
965 }
966#endif
967 }
968
969#ifdef ENABLE_DIAG
970 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
971 Print(" damage absorbed by hit zones = " + (damage - leftoverDamage), LogLevel.NORMAL);
972#endif
973 leftoverDamage = Math.Clamp(leftoverDamage, 0, float.MAX);
974
975#ifdef ENABLE_DIAG
976 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
977 {
978 Print(" leftoverDamage returned to the hull = " + leftoverDamage, LogLevel.NORMAL);
979 Print(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=", LogLevel.NORMAL);
980 }
981#endif
982 return leftoverDamage;
983 }
984
985 //------------------------------------------------------------------------------------------------
988 // TODO: This won't work properly for all the types of objects - E.G. flat boats with huge towers on them
989 // e.g x = collision point, this will say it got hit from the back, because it's inside the rear-side pyramid
990 // ||
991 // __x___||_______
992 // \ /
993 // \ /
994 // /TODO
995 // To trigger this, just crash a vehicle into something
996 // Make sure the vehicle has SCR_VehicleDamageManagerComponent with attribute "Print Relative Force" set to true.
998 {
999 IEntity owner = GetOwner();
1000 //Get local space contact position
1001 vector contact = owner.CoordToLocal(position);
1002
1003 //Get local space mins and maxs
1004 vector mins, maxs;
1005 owner.GetBounds(mins, maxs);
1006
1007 //Calculate bbox center
1008 vector bboxCenter = (maxs - mins) * 0.5 + mins;
1009
1010 //Get contact position relative to the bbox center, for correct dot product calculations later
1011 contact = contact - bboxCenter;
1012
1013 //Here we calculate all the normals of our 6 planes as well as their distances, but these broke the code completely, so I commented them out.
1014 //Naming: XZA = plane A perpendicular to the X-Z plane and so on...
1015 vector normalXZA, normalXZB, normalYZA, normalYZB, normalXYA, normalXYB;
1016 float distanceXZA, distanceXZB, distanceYZA, distanceYZB, distanceXYA, distanceXYB;
1017
1018 normalYZA = Vector(0, mins[2] - maxs[2], maxs[1] - mins[1]).Normalized();
1019 distanceYZA = vector.Dot(normalYZA, Vector(0, mins[1], mins[2]));
1020
1021 normalYZB = Vector(0, mins[2] - maxs[2], mins[1] - maxs[1]).Normalized();
1022 distanceYZB = vector.Dot(normalYZB, Vector(0, mins[1], mins[2]));
1023
1024 normalXZA = Vector(maxs[2] - mins[2], 0, mins[0] - maxs[0]).Normalized();
1025 distanceXZA = vector.Dot(normalXZA, Vector(mins[0], 0, mins[2]));
1026
1027 normalXZB = Vector(mins[2] - maxs[2], 0, mins[0] - maxs[0]).Normalized();
1028 distanceXZB = vector.Dot(normalXZB, Vector(mins[0], 0, mins[2]));
1029
1030 normalXYA = Vector(maxs[1] - mins[1], mins[0] - maxs[0], 0).Normalized();
1031 distanceXYA = vector.Dot(normalXYA, Vector(mins[0], mins[1], 0));
1032
1033 normalXYB = Vector(mins[1] - maxs[1], mins[0] - maxs[0], 0).Normalized();
1034 distanceXYB = vector.Dot(normalXYB, Vector(mins[0], mins[1], 0));
1035
1036#ifdef VEHICLE_DAMAGE_DEBUG
1037 //Debug shapes
1038 //Here we just draw all the fancy stuff
1039 m_aDebugShapes.Clear();
1040
1041 m_aDebugShapes.Insert(Shape.Create(ShapeType.BBOX, ARGB(255, 0, 255, 255), ShapeFlags.NOZBUFFER | ShapeFlags.WIREFRAME, owner.CoordToParent(mins), owner.CoordToParent(maxs)));
1042 m_aDebugShapes.Insert(Shape.CreateSphere(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER | ShapeFlags.WIREFRAME, position, 0.2));
1043
1044 vector p[2];
1045 p[0] = owner.CoordToParent(bboxCenter);
1046 p[1] = owner.CoordToParent(normalXZA);
1047 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1048
1049 p[1] = owner.CoordToParent(normalXZB);
1050 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 0, 255, 0), ShapeFlags.NOZBUFFER, p, 2));
1051
1052 p[1] = owner.CoordToParent(normalYZA);
1053 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 0, 0, 255), ShapeFlags.NOZBUFFER, p, 2));
1054
1055 p[1] = owner.CoordToParent(normalYZB);
1056 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 255), ShapeFlags.NOZBUFFER, p, 2));
1057
1058 p[1] = owner.CoordToParent(normalXYA);
1059 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 0, 255, 255), ShapeFlags.NOZBUFFER, p, 2));
1060
1061 p[1] = owner.CoordToParent(normalXYB);
1062 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 0, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1063
1064 p[1] = owner.CoordToParent(contact);
1065 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 155, 255), ShapeFlags.NOZBUFFER, p, 2));
1066#endif
1067#ifdef VEHICLE_DEBUG_OTHER
1068 //This is a temporary solution, TODO: PROPER DEBUG
1069 //Do not remove the commented scripts!
1070 m_aDebugShapes.Clear();
1071 vector p[2];
1072
1073 vector globalMins, globalMaxs;
1074 globalMins = owner.CoordToParent(mins);
1075 globalMaxs = owner.CoordToParent(maxs);
1076
1077 m_aDebugShapes.Insert(Shape.CreateSphere(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER | ShapeFlags.WIREFRAME, position, 0.2));
1078
1079 /*p[0] = globalMins;
1080 p[1] = globalMaxs;
1081 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1082
1083 p[0][0] = globalMaxs[0];
1084 p[1][0] = globalMins[0];
1085 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1086
1087 p[0] = globalMins;
1088 p[0][1] = globalMaxs[1];
1089 p[1] = globalMaxs;
1090 p[1][1] = globalMins[1];
1091 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1092
1093 p[0] = globalMins;
1094 p[0][2] = globalMaxs[2];
1095 p[1] = globalMaxs;
1096 p[1][2] = globalMins[2];
1097 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2)); */
1098
1099 //Corner lines
1100 p[0] = globalMins;
1101 p[1] = p[0];
1102 p[1][1] = globalMaxs[1];
1103 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1104
1105 p[0] = globalMins;
1106 p[0][0] = globalMaxs[0];
1107 p[1] = p[0];
1108 p[1][1] = globalMaxs[1];
1109 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1110
1111 p[0] = globalMaxs;
1112 p[1] = p[0];
1113 p[1][1] = globalMins[1];
1114 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1115
1116 p[0] = globalMaxs;
1117 p[0][0] = globalMins[0];
1118 p[1] = p[0];
1119 p[1][1] = globalMins[1];
1120 m_aDebugShapes.Insert(Shape.CreateLines(ARGB(255, 255, 0, 0), ShapeFlags.NOZBUFFER, p, 2));
1121
1122 vector mat[4];
1123 Math3D.MatrixIdentity4(mat);
1124 //Top pyramid
1125 p[0] = mins;
1126 p[0][1] = maxs[1] * 0.5;
1127 p[1] = maxs;
1128 Shape shape = Shape.Create(ShapeType.PYRAMID, ARGB(255, 0, 255, 0), ShapeFlags.NOZBUFFER, p[1], p[0]);
1129 m_aDebugShapes.Insert(shape);
1130 mat[3] = owner.GetOrigin();
1131 shape.SetMatrix(mat);
1132
1133 //Bottom pyramid
1134 p[0] = mins;
1135 p[1] = maxs;
1136 p[1][1] = p[1][1] * 0.5;
1137 shape = Shape.Create(ShapeType.PYRAMID, ARGB(255, 0, 255, 0), ShapeFlags.NOZBUFFER, p[0], p[1]);
1138 m_aDebugShapes.Insert(shape);
1139 shape.SetMatrix(mat);
1140
1141 //Right pyramid
1142 /*p[0][0] = maxs[1];
1143 p[0][1] = mins[0];
1144 p[0][2] = mins[2];
1145 p[1][0] = mins[1];
1146 p[1][1] = maxs[0];
1147 p[1][2] = maxs[2];
1148 p[0] = maxs;
1149 p[1] = mins;
1150 p[1][0] = mins[0]; */
1151 /*shape = Shape.Create(ShapeType.PYRAMID, ARGB(255, 0, 255, 0), ShapeFlags.NOZBUFFER, p[1], p[0]);
1152 m_aDebugShapes.Insert(shape);
1153 mat[0] = -vector.Up;
1154 mat[1] = vector.Right;
1155 mat[2] = vector.Forward;
1156 mat[3] = owner.GetOrigin() + maxs;
1157 shape.SetMatrix(mat);
1158 Math3D.MatrixIdentity4(mat); */
1159
1160 /*mat[0] = vector.Up;
1161 mat[1] = -vector.Right;
1162 mat[2] = vector.Forward; */
1163#endif
1164
1165 //Calculating where the contact point is relative to the plane by calculating the dot product of the contact point and the planes normal
1166 bool XZA, XZB, YZA, YZB, XYA, XYB;
1167 XZA = (vector.Dot(contact, normalXZA) > 0) - distanceXZA;
1168 XZB = (vector.Dot(contact, normalXZB) > 0) - distanceXZB;
1169 YZA = (vector.Dot(contact, normalYZA) > 0) - distanceYZA;
1170 YZB = (vector.Dot(contact, normalYZB) > 0) - distanceYZB;
1171 XYA = (vector.Dot(contact, normalXYA) > 0) - distanceXYA;
1172 XYB = (vector.Dot(contact, normalXYB) > 0) - distanceXYB;
1173
1174 //XZA = Front, right, bottom, top
1175 //XZB = Rear, right, bottom, top
1176 //YZA = Front, bottom, right, left
1177 //YZB = Rear, bottom, right, left
1178 //XYA = Front, Rear, top, left
1179 //XYB = Front, Rear, top, right
1180 // XZA XZB YZA YZB XYA XYB
1181 //Top = - - 0 0 0 0
1182 //Bottom= - - 1 1 1 1
1183 //Left = 0 1 - - 0 1
1184 //Right = 1 0 - - 1 0
1185 //Front = 0 0 1 0 - -
1186 //Rear = 1 1 0 1 - -
1187
1188 //Right
1189 if (XZA && !XZB && XYA && !XYB)
1190 return SCR_EBoxSide.RIGHT;
1191
1192 //Left
1193 if (!XZA && XZB && !XYA && XYB)
1194 return SCR_EBoxSide.LEFT;
1195
1196 //Bottom
1197 if (YZA && YZB && XYA && XYB)
1198 return SCR_EBoxSide.BOTTOM;
1199
1200 //Top
1201 if (!YZA && !YZB && !XYA && !XYB)
1202 return SCR_EBoxSide.TOP;
1203
1204 //Front
1205 if (!XZA && !XZB && YZA && !YZB)
1206 return SCR_EBoxSide.FRONT;
1207
1208 //Rear
1209 if (XZA && XZB && !YZA && YZB)
1210 return SCR_EBoxSide.REAR;
1211
1212 return SCR_EBoxSide.FRONT;
1213 }
1214
1215#ifdef WORKBENCH
1216 //------------------------------------------------------------------------------------------------
1218 float CalculateCriticalCollisionDamage()
1219 {
1220 IEntity owner = GetOwner();
1221 array<HitZone> hitZones = {};
1222 vector pointOfImpact = owner.CoordToParent(GetFrontalImpact());
1223 EDamageType damageType = EDamageType.COLLISION;
1224 float procentageSum;
1225 int count = GetSurroundingHitzones(pointOfImpact, owner.GetPhysics(), GetMaxSharedDamageDistance(), damageType, hitZones, procentageSum);
1226 if (count == 0)
1227 return 0;
1228
1229 float damage;
1230 HitZone defaultHitZone = GetDefaultHitZone();
1231
1232 // Find the total amount of damage required to destroy hit zones that will be damaged in case of a frontal collision
1233 damage = GetMinDestroyDamage(damageType, hitZones);
1234 damage = damage * procentageSum / count;
1235
1236 float defaultHitZoneDamageMultipliers = defaultHitZone.GetBaseDamageMultiplier() * defaultHitZone.GetDamageMultiplier(damageType);
1237 if (defaultHitZoneDamageMultipliers == 0)
1238 return 0;
1239
1240 float defaultHitZoneMaxDamage = (defaultHitZone.GetMaxHealth() + defaultHitZone.GetDamageReduction()) / defaultHitZoneDamageMultipliers;
1241 if (defaultHitZoneMaxDamage < defaultHitZone.GetDamageThreshold())
1242 defaultHitZoneMaxDamage += defaultHitZone.GetDamageThreshold();
1243
1244 if (defaultHitZoneMaxDamage == 0)
1245 return 0;
1246
1247 // Find how much of excessive damage will be returned to the deafult hit zone in case of an actuall collision
1248 float excessDamage = DamageSurroundingHitzones(pointOfImpact, damage, damageType, true);
1249
1250 int securityCounter = 20;//prevent infinite loop at all cost to ensure that it wont cause a loss of someones unsaved data
1251 float prc;
1252 while (damage > 0 && !float.AlmostEqual(excessDamage, defaultHitZoneMaxDamage, 0.1))
1253 {
1254 if (excessDamage == 0)
1255 return 0;
1256
1257 if (excessDamage < defaultHitZoneMaxDamage)
1258 prc = -defaultHitZoneMaxDamage / excessDamage;
1259 else
1260 prc = 1 - defaultHitZoneMaxDamage / excessDamage;
1261
1262 damage = damage - excessDamage * prc;
1263 excessDamage = DamageSurroundingHitzones(pointOfImpact, damage, damageType, true);
1264
1265 securityCounter--;
1266 if (securityCounter < 0)
1267 return 0;
1268 }
1269
1270 damage = damage / GetFrontMultiplier() * HACK_DAMAGE_CALCULATION_REDUCTION;//magic number ;_;
1271 // The reason for this magic number is that the collision position can be slightly unpredictable.
1272 // Because of that, what may end up happening is that the distribution of the damage won't have enough hit zone health to absorb the impact,
1273 // which in turn means that more damage will be passed to the hull hit zone, resulting in a vehicle explosion at a lower speed.
1274 // Thus, the precomputed damage is reduced by 10% to accommodate this bit of randomness.
1275
1276 // Damage is not valid - warn the user!
1277 if (damage < 0)
1278 {
1279 Print("Cannot destroy selected vehicle on collision", LogLevel.WARNING);
1280 return 0;
1281 }
1282
1283 return Math.Ceil(damage);
1284 }
1285#endif
1286
1287 //------------------------------------------------------------------------------------------------
1289 protected void OnWaterEnter()
1290 {
1291 Physics physics = GetOwner().GetPhysics();
1292 if (!physics)
1293 return;
1294
1295 // Don't try to apply waterFallDamage when going less than X m/s
1296 float impactSpeed = Math.AbsFloat(physics.GetVelocity()[1]);
1297 if (impactSpeed < m_fMinWaterFallDamageVelocity)
1298 return;
1299
1300 vector ownerTransform = GetOwner().GetOrigin();
1301 vector velocityBefore = Vector(0, GetOwner().GetPhysics().GetVelocity()[1], 0);
1302 vector velocityAfter = velocityBefore * m_fVelocityMultiplierOnWaterEnter;
1303 ProcessCollision(new SCR_CollisionDamageContainer(GetOwner(), GetGame().GetWorldEntity().GetTerrain(0, 0), 0, velocityBefore, velocityAfter, vector.Zero, vector.Zero, ownerTransform, vector.Zero));
1304 }
1305
1306#ifdef ENABLE_DIAG
1307 //------------------------------------------------------------------------------------------------
1308 protected void DebugSphereUpdate()
1309 {
1310 m_DebugShape.Clear();
1311 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) == 0)
1312 {
1313 m_aLocalCollisions.Clear();
1314 GetGame().GetCallqueue().Remove(DebugSphereUpdate);
1315 return;
1316 }
1317
1318 IEntity owner = GetOwner();
1319 foreach (Tuple2<vector, bool> entry : m_aLocalCollisions)
1320 {
1321 if (entry.param2)
1322 m_DebugShape.AddSphere(owner.CoordToParent(entry.param1), 0.25, Color.GREEN, ShapeFlags.WIREFRAME | ShapeFlags.ONCE);
1323 else
1324 m_DebugShape.AddSphere(owner.CoordToParent(entry.param1), 0.3, Color.RED, ShapeFlags.WIREFRAME | ShapeFlags.ONCE);
1325 }
1326 }
1327#endif
1328
1329 //------------------------------------------------------------------------------------------------
1331 override bool FilterContact(IEntity owner, IEntity other, Contact contact)
1332 {
1333 // Returning when impulse is less than 5 doesn't impact gameplay at all, but saves a lot of OnFilteredContact calls
1334 if (contact.Impulse < MIN_IMPULSE_THRESHOLD)
1335 return false;
1336
1337 return super.FilterContact(owner, other, contact);;
1338 }
1339
1340 //------------------------------------------------------------------------------------------------
1343 override protected void OnFilteredContact(IEntity owner, IEntity other, Contact contact)
1344 {
1345 if (m_ImpactEffectComponent && other)
1346 m_ImpactEffectComponent.OnImpact(other, contact.Impulse, contact.Position, contact.Normal, contact.Material2, contact.VelocityBefore1, contact.VelocityAfter1);
1347
1348 RplComponent rplComp = SCR_EntityHelper.GetEntityRplComponent(owner);
1349 if (rplComp.IsProxy()) // skip null check as damage manager cannot exist without rpl component
1350 return;// Server will deal the damage thus there is no point for proxy to process this data
1351
1352 if (contact.Impulse < m_fMinImpulse)
1353 return;
1354
1355 // Since data will be processed after some time then position has to be held in local space
1356 // This needs to be done in order to grab hit zones relevant to the impact position
1357 vector localPosition = owner.CoordToLocal(contact.Position);
1358
1359#ifdef ENABLE_DIAG
1360 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1361 {
1362 m_DebugShape.AddSphere(contact.Position, 0.3, Color.RED, ShapeFlags.WIREFRAME);
1363 if (m_aLocalCollisions.IsEmpty())
1364 GetGame().GetCallqueue().CallLater(DebugSphereUpdate, 0, true);
1365
1366 m_aLocalCollisions.Insert(new Tuple2<vector, bool>(localPosition, false));
1367 Print(" Collision detected between owner = " + owner.GetID() + " and other = " + other.GetID() + " at position = " + contact.Position, LogLevel.NORMAL);
1368 Print(" Owner Colliding surfaces = " + contact.Material1 + " | other = " + contact.Material2, LogLevel.NORMAL);
1369 Print(" Owner shape indencies = " + contact.ShapeIndex1 + " | other = " + contact.ShapeIndex2, LogLevel.NORMAL);
1370 Print(" Impuls = " + contact.Impulse);
1371 float dotMultiplier = vector.Dot(contact.VelocityAfter1.Normalized(), contact.VelocityBefore1.Normalized());
1372 vector velocityDiff = contact.VelocityBefore1 - contact.VelocityAfter1 * dotMultiplier;
1373 float impactSpeedDiff = Math.AbsFloat(velocityDiff.Length());
1374 Print(" Owner impact speed diff = " + impactSpeedDiff + "m/s (" + impactSpeedDiff * Physics.MS2KMH + "kph)", LogLevel.NORMAL);
1375 dotMultiplier = vector.Dot(contact.VelocityAfter2.Normalized(), contact.VelocityBefore2.Normalized());
1376 velocityDiff = contact.VelocityBefore2 - contact.VelocityAfter2 * dotMultiplier;
1377 impactSpeedDiff = Math.AbsFloat(velocityDiff.Length());
1378 Print(" Other impact speed diff = " + impactSpeedDiff + "m/s (" + impactSpeedDiff * Physics.MS2KMH + "kph)", LogLevel.NORMAL);
1379 }
1380#endif
1381
1383 {
1384 m_CurrentCollisions = new array<ref SCR_CollisionDamageContainer>();
1385 GetGame().GetCallqueue().CallLater(EnableDamageSystemOnFrame);
1386 }
1387
1388 SCR_CollisionDamageContainer existingCollisionInstance;
1390 {
1391 if (collision.m_Other != other)
1392 continue;
1393
1394 existingCollisionInstance = collision;
1395 break;
1396 }
1397
1398 if (!existingCollisionInstance)
1399 {
1400#ifdef ENABLE_DIAG
1401 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1402 Print(" No previous collisions cached for contact between " + owner.GetID() + " and " + other.GetID() + ". Caching this collision data!", LogLevel.NORMAL);
1403#endif
1404 m_CurrentCollisions.Insert(new SCR_CollisionDamageContainer(owner, other, contact.Impulse, contact.VelocityBefore1, contact.VelocityAfter1, contact.VelocityBefore2, contact.VelocityAfter2, localPosition, contact.Normal));
1405 return;
1406 }
1407
1408 existingCollisionInstance.UpdateImpactPosition(localPosition);
1409 if (contact.Impulse < existingCollisionInstance.m_fImpulse)
1410 {
1411#ifdef ENABLE_DIAG
1412 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1413 Print(" Detected collision has smaller impuls - discarding!", LogLevel.NORMAL);
1414#endif
1415 return;
1416 }
1417
1418 float mass;
1419 if (contact.Physics1)
1420 mass = contact.Physics1.GetMass();
1421
1422 float dotMultiplier = vector.Dot(contact.VelocityAfter1.Normalized(), contact.VelocityBefore1.Normalized());
1423 float momentumB = contact.VelocityBefore1.Length() * mass;
1424 float momentumA = contact.VelocityAfter1.Length() * mass * dotMultiplier;
1425 float totalMomentumOwner = Math.AbsFloat(momentumB - momentumA * dotMultiplier);
1426
1427#ifdef ENABLE_DIAG
1428 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1429 {
1430 Print(" owner momentum before = " + momentumB + " | momentum after = " + momentumA, LogLevel.NORMAL);
1431 Print(" owner dotMultiplier = " + dotMultiplier, LogLevel.NORMAL);
1432 }
1433#endif
1434
1435 if (totalMomentumOwner > existingCollisionInstance.m_fTotalMomentumOwner)
1436 existingCollisionInstance.m_fTotalMomentumOwner = totalMomentumOwner;
1437
1438 if (contact.Physics2)
1439 mass = contact.Physics2.GetMass();
1440 else
1441 mass = 0;
1442
1443 dotMultiplier = vector.Dot(contact.VelocityAfter2.Normalized(), contact.VelocityBefore2.Normalized());
1444 momentumB = contact.VelocityBefore2.Length() * mass;
1445 momentumA = contact.VelocityAfter2.Length() * mass * dotMultiplier;
1446 float totalMomentumOther = Math.AbsFloat(momentumB - momentumA);
1447
1448#ifdef ENABLE_DIAG
1449 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1450 {
1451 Print(" total owner momentum = " + totalMomentumOwner + " while previous was = " + existingCollisionInstance.m_fTotalMomentumOwner, LogLevel.NORMAL);
1452 Print(" other momentum before = " + momentumB + " | momentum after = " + momentumA, LogLevel.NORMAL);
1453 Print(" other dotMultiplier = " + dotMultiplier, LogLevel.NORMAL);
1454 Print(" total other momentum= " + totalMomentumOther + " while previous was = " + existingCollisionInstance.m_fTotalMomentumOther, LogLevel.NORMAL);
1455 Print(" Detected collision has larger impuls!", LogLevel.NORMAL);
1456 }
1457#endif
1458
1459 if (totalMomentumOther > existingCollisionInstance.m_fTotalMomentumOther)
1460 existingCollisionInstance.m_fTotalMomentumOther = totalMomentumOther;
1461
1462 existingCollisionInstance.m_fImpulse = contact.Impulse;
1463 }
1464
1465 //------------------------------------------------------------------------------------------------
1466 override void OnFrame(IEntity owner, float timeSlice)
1467 {
1468 super.OnFrame(owner, timeSlice);
1469
1470 if (!m_CurrentCollisions || m_CurrentCollisions.IsEmpty())
1471 {
1473 m_LastFrameCollisions.Clear();
1474
1475 DisableDamageSystemOnFrame();
1476 return;
1477 }
1478
1479 bool continuedCollision;
1481 {
1483 {
1484 ProcessCollision(collision);
1485 continue;
1486 }
1487
1488 continuedCollision = false;
1490 {
1491 if (collision.m_Other == oldCollision.m_Other)
1492 {
1493 continuedCollision = true;
1494 break;
1495 }
1496 }
1497
1498 if (continuedCollision)
1499 continue;
1500
1501 ProcessCollision(collision);
1502 }
1503
1505 m_CurrentCollisions = null;
1506 }
1507
1508 //------------------------------------------------------------------------------------------------
1511 {
1512 IEntity owner = GetOwner();
1513 // This data can be moved back to component
1514 SCR_VehicleDamageManagerComponentClass prefabData = GetPrefabData();
1515 if (!prefabData)
1516 return;
1517
1518 // Get the physics of the dynamic object (if owner is static we ignore the collision)
1519 Physics ownerPhysics = owner.GetPhysics();
1520 if (!ownerPhysics.IsDynamic())
1521 return;
1522
1523 vector impactPosition = owner.CoordToParent(collision.GetAverageImpactPosition());
1524
1525#ifdef ENABLE_DIAG
1526 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1527 {
1528 m_DebugShape.AddSphere(impactPosition, 0.2, Color.GREEN, ShapeFlags.WIREFRAME);
1529 m_aLocalCollisions.Insert(new Tuple2<vector, bool>(collision.GetAverageImpactPosition(), true));
1530 Print(" Processing collision damage at the position = " + impactPosition + " - " + GetOwner().GetID(), LogLevel.NORMAL);
1531 Print(" Cached owner speed before collision = " + collision.GetOwnerVelocityBefore().Length() * Physics.MS2KMH + "kph)", LogLevel.NORMAL);
1532 Print(" Cached other speed before collision = " + collision.GetOtherVelocityBefore().Length() * Physics.MS2KMH + "kph)", LogLevel.NORMAL);
1533 }
1534#endif
1535 int ownerResponseIndex = ownerPhysics.GetResponseIndex();
1536 // We hit a destructible that will break, static object -> deal no damage to vehicle or occupants
1537 if (!collision.m_bOtherIsDynamic && collision.m_bOtherIsDestructible && collision.m_iOtherResponseIndex - MIN_DESTRUCTION_RESPONSE_INDEX <= ownerResponseIndex - MIN_MOMENTUM_RESPONSE_INDEX)
1538 return;
1539
1540#ifdef DISABLE_VEHICLE_COLLISION_DAMAGE
1541 return;
1542#endif
1543
1544 float ownerMass = ownerPhysics.GetMass();
1545 float totalMomentum = collision.m_fTotalMomentumOwner + collision.m_fTotalMomentumOther;
1546 IEntity instigatorEntity;
1547 // Find compartment manager to damage occupants
1549 {
1550 // This is the momentum, which will be transferred to damage
1551 float momentumOverOccupantsThreshold = totalMomentum * collision.m_fDamageShare - prefabData.GetOccupantsMomentumThreshold(ownerMass);
1552
1553 // Deal damage if it's more that 0
1554 if (momentumOverOccupantsThreshold > 0)
1555 {
1556 //If the entity is dynamic, we need to look if the entity has a driver or not.
1557 //If the vehicle's speed is bigger than m_fVehicleDamageSpeedThreshold
1558 if (collision.m_bOtherIsDynamic && collision.GetOwnerVelocityBefore().LengthSq() > prefabData.GetVehicleDamageSpeedThreshold() * prefabData.GetVehicleDamageSpeedThreshold())
1559 {
1560 //If the entity has a driver:
1561 if (collision.m_OtherPilot)
1562 {
1563 //If their speed is bigger than GetVehicleDamageSpeedThreshold and they are not running away from the vehicle
1564 //(their dot product indicates that they are within 60° cone):
1565 //their driver is the instigator of the damage the occupants of this vehicle's compartments receive.
1566 //If not, the driver of this vehicle is the instigator.
1567 vector directionToOther = (collision.m_vOtherOriginAtThePointOfImpact - collision.m_vOwnerOriginAtThePointOfImpact).Normalized();
1568 if (vector.Dot(collision.GetOwnerVelocityBefore().Normalized(), directionToOther) < 0.5)
1569 instigatorEntity = collision.m_OtherPilot;
1570 }
1571 }
1572
1573 // If there was no other pilot to blame, Blame last person to damage the vehicle, if there is nobody, blame the pilot.
1574 if (!instigatorEntity)
1575 {
1576 instigatorEntity = GetInstigator().GetInstigatorEntity();
1577 Vehicle thisVehicle = Vehicle.Cast(owner);
1578 if (!instigatorEntity && thisVehicle)
1579 {
1580 ChimeraCharacter pilot = ChimeraCharacter.Cast(thisVehicle.GetPilot());
1581 CharacterControllerComponent pilotCharacterControler;
1582 if (pilot)
1583 pilotCharacterControler = pilot.GetCharacterController();
1584
1585 if (pilotCharacterControler && pilotCharacterControler.GetLifeState() != ECharacterLifeState.ALIVE)
1586 {
1587 SCR_DamageManagerComponent pilotDamageManager = pilot.GetDamageManager();
1588 if (pilotDamageManager)
1589 instigatorEntity = pilotDamageManager.GetInstigator().GetInstigatorEntity();
1590 }
1591 else
1592 {
1593 instigatorEntity = pilot;
1594 }
1595 }
1596 }
1597
1598 // Apply damage to passengers in vehicle
1599 HandlePassengerDamage(EDamageType.COLLISION, momentumOverOccupantsThreshold * prefabData.GetCharacterDamageScale(ownerMass), Instigator.CreateInstigator(instigatorEntity));
1600 }
1601 }
1602
1603 float collisionDamage = prefabData.GetVehicleDamageScale(ownerMass) * (totalMomentum - prefabData.GetVehicleMomentumThreshold(ownerMass));
1604#ifdef ENABLE_DIAG
1605 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1606 Print(" Collision damage = " + collisionDamage + " = " + prefabData.GetVehicleDamageScale(ownerMass) + " * ((" + collision.m_fTotalMomentumOwner + " + " + collision.m_fTotalMomentumOther + ") - " + prefabData.GetVehicleMomentumThreshold(ownerMass) + ")", LogLevel.NORMAL);
1607#endif
1608 // Deal damage if collision damage is over threshold
1609 if (collisionDamage > 0)
1610 {
1611 // Get hit side multiplier (e. g. front is stronger than the left/right side)
1612 float damageSideMultiplier = GetSideDamageMultiplier(GetHitDirection(impactPosition));
1613
1614#ifdef ENABLE_DIAG
1615 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS) != 0)
1616 Print(" Collision damage = " + (collisionDamage*damageSideMultiplier*collision.m_fDamageShare) + " = " + collisionDamage + " * " + damageSideMultiplier + " * " + collision.m_fDamageShare, LogLevel.NORMAL);
1617#endif
1618 collisionDamage *= damageSideMultiplier * collision.m_fDamageShare;
1619 // Handle damage requires a matrix, so we create an empty one
1620 vector empty[3];
1621 empty[0] = vector.Zero;
1622 empty[1] = vector.Zero;
1623 empty[2] = vector.Zero;
1624
1625 SCR_DamageContext damageContext = new SCR_DamageContext(EDamageType.COLLISION, DamageSurroundingHitzones(impactPosition, collisionDamage, EDamageType.COLLISION), empty, GetOwner(), GetDefaultHitZone(), Instigator.CreateInstigator(instigatorEntity), null, -1, -1);
1626 damageContext.damageEffect = new SCR_CollisionDamageEffect();
1627 // finally we deal damage
1628 HandleDamage(damageContext);
1629 }
1630 }
1631
1632 //------------------------------------------------------------------------------------------------
1634 static ScriptInvokerInt GetOnVehicleDestroyed()
1635 {
1636 if (!s_OnVehicleDestroyed)
1637 s_OnVehicleDestroyed = new ScriptInvokerInt();
1638
1639 return s_OnVehicleDestroyed;
1640 }
1641
1642 //------------------------------------------------------------------------------------------------
1644 static ScriptInvoker GetOnVehicleDamageStateChanged()
1645 {
1646 if (!s_OnVehicleDamageStateChanged)
1647 s_OnVehicleDamageStateChanged = new ScriptInvoker();
1648
1649 return s_OnVehicleDamageStateChanged;
1650 }
1651
1652 //------------------------------------------------------------------------------------------------
1654 override void OnDamageStateChanged(EDamageState newState, EDamageState previousDamageState, bool isJIP)
1655 {
1656 super.OnDamageStateChanged(newState, previousDamageState, isJIP);
1657
1658 if (s_OnVehicleDamageStateChanged)
1659 s_OnVehicleDamageStateChanged.Invoke(this);
1660
1661 HitZone defaultHitZone = GetDefaultHitZone();
1662
1663 SCR_VehicleBuoyancyComponent vehicleBuoyancy = SCR_VehicleBuoyancyComponent.Cast(GetOwner().FindComponent(SCR_VehicleBuoyancyComponent));
1664 if (vehicleBuoyancy)
1665 {
1666 vehicleBuoyancy.SetHealth(defaultHitZone.GetDamageStateThreshold(newState));
1667
1668 if (newState == EDamageState.DESTROYED)
1669 vehicleBuoyancy.GetOnWaterEnter().Remove(OnWaterEnter);
1670 }
1671
1672 if (s_OnVehicleDestroyed && newState == EDamageState.DESTROYED)
1673 {
1674 s_OnVehicleDestroyed.Invoke(GetInstigator().GetInstigatorPlayerID());
1675
1676 float fireRate;
1678 UpdateVehicleFireState(fireRate, 0);
1679
1680 if (fireRate > 0)
1681 {
1682 World world = GetOwner().GetWorld();
1683 FireDamageSystem system = FireDamageSystem.Cast(world.FindSystem(FireDamageSystem));
1684 if (system)
1685 system.Register(this);
1686 }
1687 }
1688 }
1689
1690 //------------------------------------------------------------------------------------------------
1692 override protected void OnDamage(notnull BaseDamageContext damageContext)
1693 {
1694 super.OnDamage(damageContext);
1695
1696 if (damageContext.damageType == EDamageType.EXPLOSIVE && damageContext.struckHitZone == GetDefaultHitZone())
1697 HandlePassengerDamage(EDamageType.EXPLOSIVE, damageContext.damageValue, damageContext.instigator);
1698 }
1699
1700 //------------------------------------------------------------------------------------------------
1702 override bool CanBeHealed(bool ignoreHealingDOT = true)
1703 {
1704 // Get drowned engine
1705 BaseVehicleNodeComponent vehicleNode = BaseVehicleNodeComponent.Cast(GetOwner().FindComponent(BaseVehicleNodeComponent));
1706 if (vehicleNode)
1707 {
1708 VehicleControllerComponent vehicleController = VehicleControllerComponent.Cast(vehicleNode.FindComponent(VehicleControllerComponent));
1709 if (vehicleController && vehicleController.GetEngineDrowned())
1710 return true;
1711 }
1712
1713 return super.CanBeHealed();
1714 }
1715
1716 //------------------------------------------------------------------------------------------------
1719 override void FullHeal(bool ignoreHealingDOT = true)
1720 {
1721 // Fix drowned engine
1722 BaseVehicleNodeComponent vehicleNode = BaseVehicleNodeComponent.Cast(GetOwner().FindComponent(BaseVehicleNodeComponent));
1723 if (vehicleNode)
1724 {
1725 VehicleControllerComponent vehicleController = VehicleControllerComponent.Cast(vehicleNode.FindComponent(VehicleControllerComponent));
1726 if (vehicleController && vehicleController.GetEngineDrowned())
1727 vehicleController.SetEngineDrowned(false);
1728 }
1729
1730 // Repair everything else that can be repaired
1731 super.FullHeal(ignoreHealingDOT);
1732 }
1733
1734 //------------------------------------------------------------------------------------------------
1736 void HandlePassengerDamage(EDamageType damageType, float damage, notnull Instigator instigator)
1737 {
1738 if (damageType == EDamageType.COLLISION)
1739 {
1740 // Damage occupants of the car that experienced a collision
1741 m_CompartmentManager.DamageOccupants(damage, EDamageType.COLLISION, instigator);
1742 // if a collision was capable of causing more than minCollisionEjectionDamageThreshold damage, try to eject passengers
1743
1745 m_CompartmentManager.EjectRandomOccupants(GetCollisionDamageEjectionChance(), true);
1746
1747 return;
1748 }
1749
1750 if (damageType == EDamageType.EXPLOSIVE)
1751 {
1752 // An explosion of minExplosionEjectionDamageThreshold or larger is capable of ejecting occupants.
1754 m_CompartmentManager.EjectRandomOccupants(GetExplosionDamageEjectionChance(), true);
1755
1756 return;
1757 }
1758 }
1759
1760 //------------------------------------------------------------------------------------------------
1762 // Takes care of updating the reponse index
1764 {
1765 Physics physics = GetOwner().GetPhysics();
1766 if (!physics)
1767 return;
1768
1769 float momentum = physics.GetMass() * physics.GetVelocity().Length(); // Calculate current vehicle's momentum
1770 float currentIndexMinMomentum = s_mResponseIndexMomentumMap.Get(physics.GetResponseIndex());
1771 int currentIndex = physics.GetResponseIndex();
1772
1773 while (momentum > currentIndexMinMomentum && currentIndex < MAX_RESPONSE_INDEX)
1774 {
1775 currentIndex++;
1776 currentIndexMinMomentum = s_mResponseIndexMomentumMap.Get(currentIndex);
1777 if (momentum < currentIndexMinMomentum) // We are at a point, where current momentum isn't big enough for this index
1778 {
1779 // We go one index down and break from the loop
1780 currentIndex--;
1781 currentIndexMinMomentum = s_mResponseIndexMomentumMap.Get(currentIndex);
1782 break;
1783 }
1784 }
1785
1786 while (momentum < currentIndexMinMomentum && currentIndex > MIN_RESPONSE_INDEX)
1787 {
1788 currentIndex--;
1789 currentIndexMinMomentum = s_mResponseIndexMomentumMap.Get(currentIndex);
1790 if (momentum > currentIndexMinMomentum) // We are at a point, where current momentum is big enough for this index
1791 break;
1792 }
1793
1794 physics.SetResponseIndex(currentIndex);
1795 }
1796
1797 //------------------------------------------------------------------------------------------------
1798 [RplRpc(RplChannel.Reliable, RplRcver.Server)]
1799 protected void RPC_OnPhysicsActive(bool activeState)
1800 {
1801 ToggleResponseIndexTicking(GetOwner(), activeState);
1802 }
1803
1804 //------------------------------------------------------------------------------------------------
1808 void ToggleResponseIndexTicking(IEntity owner, bool activeState)
1809 {
1810 // Always remove first so we don't end up double-registering
1811 GetGame().GetCallqueue().Remove(TickResponseIndexCheck);
1812 if (activeState)
1813 GetGame().GetCallqueue().CallLater(TickResponseIndexCheck, 500, true);
1814 }
1815
1816 //------------------------------------------------------------------------------------------------
1820 void EOnPhysicsActive(IEntity owner, bool activeState)
1821 {
1822 Vehicle vehicle = Vehicle.Cast(owner);
1823 if (!vehicle)
1824 return;
1825
1826 RplComponent rplComponent = vehicle.GetRplComponent();
1827 if (!rplComponent)
1828 return;
1829
1830 if (rplComponent.IsProxy() && !rplComponent.IsOwner())
1831 {
1832 // Make sure to always deactivate ticking on remote proxies
1833 if (!activeState)
1834 RPC_OnPhysicsActive(activeState);
1835 }
1836 else if (rplComponent.IsOwner() || !rplComponent.IsProxy())
1837 {
1838 RPC_OnPhysicsActive(activeState);
1839 }
1840 }
1841
1842 //---- REFACTOR NOTE START: This code will need to be refactored as current implementation is not conforming to the standards ----
1843 // TODO: Flammability should become damage effect
1844 //------------------------------------------------------------------------------------------------
1847 void RegisterFlammableHitZone(notnull SCR_FlammableHitZone hitZone)
1848 {
1850 m_aFlammableHitZones.Insert(hitZone);
1851 else
1852 m_aFlammableHitZones = {hitZone};
1853 }
1854
1855 //------------------------------------------------------------------------------------------------
1858 void UnregisterFlammableHitZone(notnull SCR_FlammableHitZone hitZone)
1859 {
1861 m_aFlammableHitZones.RemoveItem(hitZone);
1862
1863 if (m_aFlammableHitZones.IsEmpty())
1864 m_aFlammableHitZones = null;
1865 }
1866 //---- REFACTOR NOTE END ----
1867
1868 //---- REFACTOR NOTE START: This code will need to be refactored as current implementation is not conforming to the standards ----
1869 // TODO: It should be possible to work without explicit flammable hitZones, perhaps supply crates could have this ability though simplified
1870 //------------------------------------------------------------------------------------------------
1871 override void UpdateFireDamage(float timeSlice)
1872 {
1874 return;
1875
1876 float fireRate;
1877 UpdateVehicleFireState(fireRate, timeSlice);
1878 UpdateFuelTankFireState(fireRate, timeSlice);
1879 UpdateSuppliesFireState(fireRate, timeSlice);
1880 }
1881
1882 //------------------------------------------------------------------------------------------------
1888
1889 //------------------------------------------------------------------------------------------------
1890 protected void SetVehicleFireState(SCR_ESecondaryExplosionScale state, vector origin = vector.Zero)
1891 {
1892 if (m_eVehicleFireState == state)
1893 return;
1894
1895 m_eVehicleFireState = state;
1896 m_vVehicleFireOrigin = origin;
1897 Replication.BumpMe();
1898
1900 }
1901
1902 //------------------------------------------------------------------------------------------------
1910
1911 //------------------------------------------------------------------------------------------------
1912 protected void SetFuelTankFireState(SCR_ESecondaryExplosionScale state, vector origin = vector.Zero)
1913 {
1914 if (m_eFuelTankFireState == state)
1915 return;
1916
1917 m_eFuelTankFireState = state;
1918 m_vFuelTankFireOrigin = origin;
1919 Replication.BumpMe();
1920
1922 }
1923
1924 //------------------------------------------------------------------------------------------------
1932
1933 //------------------------------------------------------------------------------------------------
1934 protected void SetSuppliesFireState(SCR_ESecondaryExplosionScale state, vector origin = vector.Zero)
1935 {
1936 if (m_eSuppliesFireState == state)
1937 return;
1938
1939 m_eSuppliesFireState = state;
1940 m_vSuppliesFireOrigin = origin;
1941 Replication.BumpMe();
1942
1944 }
1945
1946 //------------------------------------------------------------------------------------------------
1947 protected void UpdateVehicleFireState(out float fireRate, float timeSlice)
1948 {
1949 vector averagePosition = GetSecondaryExplosionPosition(SCR_FlammableHitZone, fireRate);
1950
1951 // Fire area damage
1952 m_fVehicleFireDamageTimeout -= timeSlice;
1954 {
1955 EntitySpawnParams spawnParams = new EntitySpawnParams();
1956 spawnParams.Transform[3] = averagePosition;
1957 ResourceName fireDamage = GetSecondaryExplosion(fireRate, SCR_ESecondaryExplosionType.FUEL, fire: true);
1958 SecondaryExplosion(fireDamage, GetInstigator(), spawnParams);
1959
1960 // Constant intervals for secondary damage
1961 m_fVehicleFireDamageTimeout = m_fSecondaryFireDamageDelay;
1962 }
1963
1964 SCR_ESecondaryExplosionScale vehicleFireState = GetSecondaryExplosionScale(fireRate, SCR_ESecondaryExplosionType.FUEL);
1965 SetVehicleFireState(vehicleFireState, averagePosition);
1966 }
1967
1968 //------------------------------------------------------------------------------------------------
1969 protected void UpdateFuelTankFireState(float fireRate, float timeSlice)
1970 {
1971 if (!m_FuelManager)
1972 {
1973 SetFuelTankFireState(SCR_ESecondaryExplosionScale.NONE);
1974 return;
1975 }
1976
1977 if (fireRate <= 0)
1978 {
1979 SetFuelTankFireState(SCR_ESecondaryExplosionScale.NONE);
1980 return;
1981 }
1982
1983 if (GetState() == EDamageState.DESTROYED)
1984 {
1985 SetFuelTankFireState(SCR_ESecondaryExplosionScale.NONE);
1986 return;
1987 }
1988
1989 float burningFuel;
1990 vector averagePosition = GetSecondaryExplosionPosition(SCR_FuelHitZone, burningFuel);
1991 if (float.AlmostEqual(burningFuel, 0))
1992 {
1993 SetFuelTankFireState(SCR_ESecondaryExplosionScale.NONE);
1994 return;
1995 }
1996
1997 // Fire area damage
1998 float fuelBurnRate;
1999 m_fFuelTankFireDamageTimeout -= timeSlice;
2001 {
2002 EntitySpawnParams spawnParams = new EntitySpawnParams();
2003 spawnParams.Transform[3] = averagePosition;
2004 ResourceName fireDamage = GetSecondaryExplosion(burningFuel / fireRate, SCR_ESecondaryExplosionType.FUEL, fire: true);
2005 SecondaryExplosion(fireDamage, GetInstigator(), spawnParams);
2006
2007 // Constant intervals for secondary damage
2008 m_fFuelTankFireDamageTimeout = m_fSecondaryFireDamageDelay;
2009 fuelBurnRate = fireRate * m_fSecondaryFireDamageDelay * m_fFuelTankFireDamageRate;
2010 }
2011
2012 // Consume fuel
2013 if (fuelBurnRate > 0)
2014 {
2015 SCR_FuelNode fuelTank;
2016 float fuelLoss;
2017 array<BaseFuelNode> fuelTanks = {};
2018 m_FuelManager.GetFuelNodesList(fuelTanks);
2019 foreach (BaseFuelNode fuelNode : fuelTanks)
2020 {
2021 fuelTank = SCR_FuelNode.Cast(fuelNode);
2022 if (!fuelTank)
2023 continue;
2024
2025 // Burn fuel based on fuel tank health and amount of fuel vs burning fuel amount
2026 fuelLoss = fuelBurnRate * (1 - fuelTank.GetHealth()) * fuelNode.GetFuel() / burningFuel;
2027 if (fuelLoss > 0)
2028 fuelTank.SetFuel(fuelTank.GetFuel() - fuelLoss);
2029 }
2030 }
2031
2032 SCR_ESecondaryExplosionScale fuelTankFireState = GetSecondaryExplosionScale(fireRate, SCR_ESecondaryExplosionType.FUEL);
2033 SetFuelTankFireState(fuelTankFireState, averagePosition);
2034 }
2035
2036 //------------------------------------------------------------------------------------------------
2037 protected void UpdateSuppliesFireState(float fireRate, float timeSlice)
2038 {
2039 if (fireRate <= 0)
2040 {
2041 SetSuppliesFireState(SCR_ESecondaryExplosionScale.NONE);
2042 return;
2043 }
2044
2045 if (GetState() == EDamageState.DESTROYED)
2046 {
2047 SetSuppliesFireState(SCR_ESecondaryExplosionScale.NONE);
2048 return;
2049 }
2050
2052 if (!encapsulator)
2053 {
2054 SetSuppliesFireState(SCR_ESecondaryExplosionScale.NONE);
2055 return;
2056 }
2057
2058 float totalResources = encapsulator.GetAggregatedResourceValue();
2059 if (totalResources <= 0)
2060 {
2061 SetSuppliesFireState(SCR_ESecondaryExplosionScale.NONE);
2062 return;
2063 }
2064
2065 SCR_ResourceContainerQueueBase containerQueue = encapsulator.GetContainerQueue();
2066 int containerCount = encapsulator.GetContainerCount();
2067
2068 SCR_ResourceContainer container;
2069 float weight;
2071
2072 vector averagePosition = GetOwner().CoordToLocal(encapsulator.GetOwnerOrigin());
2073
2074 // Get the weighed average position of explosion relative to encapsulator
2075 for (int i; i < containerCount; i++)
2076 {
2077 container = containerQueue.GetContainerAt(i);
2078 if (!container)
2079 continue;
2080
2081 // Determine secondary explosion position
2082 weight = container.GetResourceValue() / totalResources;
2083 if (weight <= 0)
2084 continue;
2085
2086 position = GetOwner().CoordToLocal(container.GetOwnerOrigin());
2087 averagePosition += position * weight;
2088 }
2089
2090 SCR_ESecondaryExplosionScale suppliesFireState = GetSecondaryExplosionScale(fireRate, SCR_ESecondaryExplosionType.RESOURCE);
2091 SetSuppliesFireState(suppliesFireState, averagePosition);
2092
2093 // Fire area damage
2094 float suppliesDamage;
2095 m_fSuppliesFireDamageTimeout -= timeSlice;
2097 {
2098
2099 EntitySpawnParams spawnParams = EntitySpawnParams();
2100 spawnParams.Transform[3] = averagePosition;
2101 ResourceName fireDamage = GetSecondaryExplosion(fireRate, SCR_ESecondaryExplosionType.RESOURCE, fire: true);
2102 SecondaryExplosion(fireDamage, GetInstigator(), spawnParams);
2103
2104 // Constant intervals for secondary damage
2105 m_fSuppliesFireDamageTimeout = m_fSecondaryFireDamageDelay;
2106 suppliesDamage = Math.Ceil(fireRate * m_fSecondaryFireDamageDelay * m_fSuppliesFireDamageRate);
2107 }
2108
2109 if (containerCount > 1)
2110 suppliesDamage /= containerCount;
2111
2112 for (int i; i < containerCount; i++)
2113 {
2114 container = containerQueue.GetContainerAt(i);
2115 if (container)
2116 container.DecreaseResourceValue(suppliesDamage);
2117 }
2118 }
2119 //---- REFACTOR NOTE END ----
2120
2121 //------------------------------------------------------------------------------------------------
2124 {
2125 return m_fMinImpulse;
2126 }
2127
2128 //------------------------------------------------------------------------------------------------
2131 {
2132 s_mResponseIndexMomentumMap.Insert(SCR_EPhysicsResponseIndex.TINY_MOMENTUM, 0);
2133 s_mResponseIndexMomentumMap.Insert(SCR_EPhysicsResponseIndex.SMALL_MOMENTUM, 6666.6); // Approx UAZ (1600kg) at 15 km/h in kJ
2134 s_mResponseIndexMomentumMap.Insert(SCR_EPhysicsResponseIndex.MEDIUM_MOMENTUM, 22222.2); // 50 km/h
2135 s_mResponseIndexMomentumMap.Insert(SCR_EPhysicsResponseIndex.LARGE_MOMENTUM, 44444.4); // 100 km/h
2136 s_mResponseIndexMomentumMap.Insert(SCR_EPhysicsResponseIndex.HUGE_MOMENTUM, 133333.3); // 300 km/h
2137 }
2138
2139 //------------------------------------------------------------------------------------------------
2145 */
2146 protected override bool ShouldOverrideInstigator(notnull Instigator currentInstigator, notnull Instigator newInstigator)
2147 {
2148 // if the vehicle is on fire and we have a valid fireInstigator, do not allow any new instigators to override it
2149 SCR_FlammableHitZone flammableHitZone = SCR_FlammableHitZone.Cast(GetDefaultHitZone());
2150 Instigator fireInstigator = flammableHitZone.GetFireInstigator();
2151 if (fireInstigator && flammableHitZone && IsOnFire(flammableHitZone) && flammableHitZone.GetFireInstigator() != newInstigator)
2152 return false;
2153
2154 // Don't override valid instigators with invalid instigators
2155 if (newInstigator.GetInstigatorType() == InstigatorType.INSTIGATOR_NONE)
2156 return false;
2157
2158 return true;
2159 }
2160
2161 //------------------------------------------------------------------------------------------------
2162 // constructor
2167 {
2168 //Can be removed when this event is called on components
2169 Vehicle ownerVehicle = Vehicle.Cast(ent);
2170 if (ownerVehicle)
2171 ownerVehicle.GetOnPhysicsActive().Insert(EOnPhysicsActive);
2172
2173 if (!s_mResponseIndexMomentumMap.Find(SCR_EPhysicsResponseIndex.TINY_MOMENTUM, null))
2175
2176#ifdef ENABLE_DIAG
2177 if (Replication.IsServer()) //collision data is only processed on the server so there is no reason to have this debug on client and RplComponent will probably not exist at this point
2178 DiagMenu.RegisterItem(SCR_DebugMenuID.DEBUGUI_VEHICLES_COLLISIONS, "", "Collisions debug prints", "Vehicles", "disabled,base,extended,all");
2179#endif
2180 }
2181
2182 //------------------------------------------------------------------------------------------------
2191}
SCR_DebugMenuID
This enum contains all IDs for DiagMenu entries added in script.
Definition DebugMenuID.c:4
EHitZoneGroup
ArmaReforgerScripted GetGame()
Definition game.c:1398
override string GetID(string fileName, string varName, array< BaseContainer > objects, array< int > indexes)
override bool FilterContact(IEntity owner, IEntity other, Contact contact)
Armor doesn't take collisiondamage.
SCR_CacheNoteComponentClass ScriptComponentClass RplProp()] protected ref array< string > m_aLines
override bool HandleDamage(BaseDamageContext damageContext, IEntity owner)
SCR_CharacterBloodHitZone OnDamage
Resilience - incapacitation or death, depending on game mode settings.
SCR_CharacterSoundComponentClass GetComponentData()
bool IsOnFire(HitZone hitZone=null)
void UpdateFireParticles(vector position, out ParticleEffectEntity particles, SCR_ESecondaryExplosionScale state, SCR_ESecondaryExplosionType fireType, EResourceType resourceType=EResourceType.SUPPLIES)
ResourceName GetSecondaryExplosion(float value, SCR_ESecondaryExplosionType explosionType, EResourceType resourceType=EResourceType.SUPPLIES, bool fire=false, out bool hasData=false)
Determine secondary explosion prefab based on explosion value, type and resource type if defined.
vector GetSecondaryExplosionPosition(typename hitZoneType, out float totalWeight=0)
Get weighed average position of explosion for hitzones of specified type.
void SecondaryExplosion(ResourceName prefabName, notnull Instigator instigator, notnull EntitySpawnParams spawnParams)
Spawn secondary explosion.
SCR_ResourceEncapsulator GetResourceEncapsulator(EResourceType suppliesType=EResourceType.SUPPLIES)
float GetMinDestroyDamage(EDamageType damageType, array< HitZone > hitzones)
SCR_DestructibleEntityClass MIN_MOMENTUM_RESPONSE_INDEX
vector position
SCR_EBoxSide
Definition SCR_EBoxSide.c:3
override float GetSecondaryExplosionScale()
override void UpdateMovementDamage()
override void OnDamageStateChanged(EDamageState newState, EDamageState previousDamageState, bool isJIP)
override void SetEngineFunctional(bool functional)
override void UnregisterVehicleHitZone(HitZone hitZone)
override void RegisterVehicleHitZone(notnull HitZone hitZone)
override void UpdateVehicleState()
void ParticleEffectEntity(IEntitySource src, IEntity parent)
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
override bool ShouldOverrideInstigator(notnull Instigator currentInstigator, notnull Instigator newInstigator)
override void OnFilteredContact(IEntity owner, IEntity other, Contact contact)
ScriptInvokerBase< ScriptInvokerIntMethod > ScriptInvokerInt
SCR_ESecondaryExplosionType
Exploding material type.
SCR_BaseCompartmentManagerComponent m_CompartmentManager
float GetVehicleDestructionSpeed()
float m_fFuelTankFireDamageTimeout
void OnSuppliesFireStateChanged()
ParticleEffectEntity m_FuelTankFireParticle
SignalsManagerComponent m_SignalsManager
void RPC_OnPhysicsActive(bool activeState)
float GetOccupantsSpeedDeath()
override void FullHeal(bool ignoreHealingDOT=true)
float m_fDamageScaleToCharacter
enum SCR_EPhysicsResponseIndex MIN_IMPULSE_THRESHOLD
void UpdateFuelTankFireState(float fireRate, float timeSlice)
float m_fMomentumVehicleThreshold
float GetCollisionDamageEjectionChance()
int GetMinCollisionEjectionDamageThreshold()
void OnWaterEnter()
Determine whether collision into water is severe enough to apply damage, then get velocities an manua...
SCR_ESecondaryExplosionScale m_eVehicleFireState
void SetGearboxFunctional(bool functional)
void ToggleResponseIndexTicking(IEntity owner, bool activeState)
SCR_ImpactEffectComponent m_ImpactEffectComponent
ref array< ref SCR_CollisionDamageContainer > m_CurrentCollisions
void HandlePassengerDamage(EDamageType damageType, float damage, notnull Instigator instigator)
Damage and eject passengers based on vehicle taking damage.
override bool CanBeHealed(bool ignoreHealingDOT=true)
Return true if there is damage that can be repaired.
float m_fOccupantsDamageSpeedThreshold
void UpdateVehicleFireState(out float fireRate, float timeSlice)
int GetMinExplosionDamageEjectionThreshold()
void ProcessCollision(notnull SCR_CollisionDamageContainer collision)
Processing of cached collision data in order to deal damage to the vhehicle and its passengers.
void SetEngineEfficiency(float efficiency)
const float HITZONE_DAMAGE_VALUE_THRESHOLD
ref array< SCR_FlammableHitZone > m_aFlammableHitZones
SCR_VehicleDamageManagerComponentClass GetPrefabData()
void RegisterFlammableHitZone(notnull SCR_FlammableHitZone hitZone)
string m_sFuelTankFireStateSignal
const int MAX_RESPONSE_INDEX
float GetCharacterDamageScale(float ownerMass)
float GetEngineMalfunctionThreshold()
int m_iMinCollisionEjectionDamageThreshold
float m_iExplosionDamageEjectionChance
float GetMaxSharedDamageDistance()
float GetVehicleDamageSpeedThreshold()
const int MIN_RESPONSE_INDEX
void UnregisterFlammableHitZone(notnull SCR_FlammableHitZone hitZone)
ref array< HitZone > m_aVehicleHitZones
Common vehicle features that will influence its simulation.
float GetVehicleMomentumThreshold(float ownerMass)
float m_iCollisionDamageEjectionChance
string m_sSuppliesFireStateSignal
void SetSuppliesFireState(SCR_ESecondaryExplosionScale state, vector origin=vector.Zero)
SCR_EBoxSide GetHitDirection(vector position)
SCR_ESecondaryExplosionScale m_eFuelTankFireState
float m_fSuppliesFireDamageTimeout
float GetVehicleDamageScale(float ownerMass)
override void UpdateFireDamage(float timeSlice)
void SetGearboxEfficiency(float efficiency)
void UpdateSuppliesFireState(float fireRate, float timeSlice)
void SCR_VehicleDamageManagerComponent(IEntityComponentSource src, IEntity ent, IEntity parent)
int GetMinExplosionEjectionDamageThreshold()
float GetSideDamageMultiplier(SCR_EBoxSide side)
void OnFuelTankFireStateChanged()
float GetOccupantsDamageSpeedThreshold()
FuelManagerComponent m_FuelManager
CompartmentControllerComponent m_Controller
int GetSurroundingHitzones(vector position, notnull Physics physics, float maxDistance, EDamageType damageType, out notnull array< HitZone > outHitZones, out float hitZonesDistancePercentSum=0, out map< HitZone, float > outHitZoneDistanceProcentageMap=null)
float m_fVehicleFireDamageTimeout
float m_fVelocityMultiplierOnWaterEnter
float GetExplosionDamageEjectionChance()
string m_sVehicleFireStateSignal
ParticleEffectEntity m_SuppliesFireParticle
void ~SCR_VehicleDamageManagerComponent()
int m_iMinExplosionEjectionDamageThreshold
void EOnPhysicsActive(IEntity owner, bool activeState)
int GetMinCollisionDamageEjectionThreshold()
void SetVehicleFireState(SCR_ESecondaryExplosionScale state, vector origin=vector.Zero)
float GetOccupantsMomentumThreshold(float ownerMass)
float m_fMomentumOccupantsThreshold
float DamageSurroundingHitzones(vector position, float damage, EDamageType damageType, bool dontApply=false)
SCR_ESecondaryExplosionScale m_eSuppliesFireState
const int CACHED_COLLISION_PROCESSING_DELAY
VehicleBaseSimulation m_Simulation
void SetFuelTankFireState(SCR_ESecondaryExplosionScale state, vector origin=vector.Zero)
ref array< ref SCR_CollisionDamageContainer > m_LastFrameCollisions
enum EVehicleType IEntity
Definition Color.c:13
Diagnostic and developer menu system.
Definition DiagMenu.c:18
void Register(SCR_DamageManagerComponent component)
proto external Managed FindComponent(typename typeName)
proto external vector GetOrigin()
proto external Physics GetPhysics()
proto external void GetBounds(out vector mins, out vector maxs)
proto external BaseWorld GetWorld()
proto external EntityID GetID()
proto external vector CoordToParent(vector coord)
proto external vector CoordToLocal(vector coord)
Definition Math.c:13
Main replication API.
Definition Replication.c:14
static RplComponent GetEntityRplComponent(notnull IEntity entity)
static void StopParticleEmissionAndLights(notnull ParticleEffectEntity particleEntity, int lightEmitterID=0)
bool DecreaseResourceValue(float value, bool notifyChange=true)
SCR_ResourceContainer GetContainerAt(int index)
override float GetAggregatedResourceValue()
override SCR_ResourceContainerQueueBase GetContainerQueue()
Instance of created debug visualizer.
Definition Shape.c:14
Definition World.c:16
Definition Types.c:486
enum EPhysicsLayerPresets Vehicle
Definition gameLib.c:24
void EntitySpawnParams()
Definition gameLib.c:130
IEntity GetOwner()
Owner entity of the fuel tank.
ECharacterLifeState
proto external vector GetVelocity()
InstigatorType
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
ShapeType
Definition ShapeType.c:13
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
ShapeFlags
Definition ShapeFlags.c:13
@ NORMAL
Definition LogLevel.c:18
SCR_FieldOfViewSettings Attribute
GenericWorldEntityClass GenericEntityClass GetTerrain(float x, float z)
EntityEvent
Various entity events.
Definition EntityEvent.c:14
EDamageType
Definition EDamageType.c:13
EDamageState
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
@ MAX
proto native vector Vector(float x, float y, float z)
proto int ARGB(int a, int r, int g, int b)
ScriptInvokerBase< func > ScriptInvoker
Definition tools.c:134