Arma Reforger Explorer  1.1.0.42
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
SCR_DestructibleEntity.c
Go to the documentation of this file.
1 //------------------------------------------------------------------------------------------------
2 [EntityEditorProps(category: "GameScripted/Destructibles", visible: false, dynamicBox: true)]
4 {
5  [Attribute("0.05", UIWidgets.Slider, "Contact momentum to damage multiplier", "0.01 10 0.01", category: "Destruction Setup")]
6  float m_fMomentumToDamageScale;
7 
8  [Attribute("", UIWidgets.Object, "List of objects (particles, debris, etc) to spawn on destruction of the object", category: "Destruction FX")]
9  ref array<ref SCR_BaseSpawnable> m_aDestroySpawnObjects;
10 
11  [Attribute("0", uiwidget: UIWidgets.ComboBox, "Type of material for destruction sound", "", ParamEnumArray.FromEnum(SCR_EMaterialSoundTypeBreak))]
12  SCR_EMaterialSoundTypeBreak m_eMaterialSoundType;
13 };
14 
15 //------------------------------------------------------------------------------------------------
17 {
18  const int MAX_PHASES_BIT_SIZE = 4; // Max 16 phases are supported now = 4 bits
19  float m_fHitDamage;
20  EDamageType m_eDamageType;
21  vector m_vHitPosition;
22  vector m_vHitDirection; //Normalized
23  vector m_vHitNormal; //Normalized
24  bool m_bTotalDestruction;
25  int m_iPreviousPhase;
26 
27  //------------------------------------------------------------------------------------------------
28  void SaveNormalizedVector(ScriptBitWriter w, vector norm)
29  {
30  w.WriteHalf(norm[0]);
31  w.WriteHalf(norm[1]);
32  w.WriteHalf(norm[2]);
33  }
34 
35  //------------------------------------------------------------------------------------------------
36  void LoadNormalizedVector(ScriptBitReader r, out vector norm)
37  {
38  float x;
39  float y;
40  float z;
41 
42  r.ReadHalf(x);
43  r.ReadHalf(y);
44  r.ReadHalf(z);
45 
46  norm = {x,y,z};
47  }
48 
49  //------------------------------------------------------------------------------------------------
50  bool Save(ScriptBitWriter w)
51  {
52  SaveNormalizedVector(w, m_vHitDirection);
53  w.WriteFloat(m_fHitDamage);
54  SaveNormalizedVector(w, m_vHitNormal);
55  w.WriteInt(m_eDamageType);
56 
57  w.WriteVector(m_vHitPosition); // Maybe we should make this relative to the entity, so it's smaller numbers and we can also write it as half
58  w.WriteBool(m_bTotalDestruction);
59  w.WriteInt(m_iPreviousPhase);
60 
61  return true;
62  }
63 
64  //------------------------------------------------------------------------------------------------
65  bool Load(ScriptBitReader r)
66  {
67  LoadNormalizedVector(r, m_vHitDirection);
68  r.ReadFloat(m_fHitDamage);
69  LoadNormalizedVector(r, m_vHitNormal);
70  r.ReadInt(m_eDamageType);
71  r.ReadVector(m_vHitPosition);
72  r.ReadBool(m_bTotalDestruction);
73  r.ReadInt(m_iPreviousPhase);
74 
75  return true;
76  }
77 }
78 
79 //------------------------------------------------------------------------------------------------
81 {
82  const float SIMULATION_IMPRECISION_MULTIPLIER = 1.1;
83  protected static const int MIN_MOMENTUM_RESPONSE_INDEX = 1;
84  protected static const int MAX_MOMENTUM_RESPONSE_INDEX = 5;
85  protected static const int MIN_DESTRUCTION_RESPONSE_INDEX = 6;
86  protected static const string DAMAGE_PHASE_SIGNAL_NAME = "DamagePhase";
87  static const int MAX_DESTRUCTION_RESPONSE_INDEX = 10;
88  static const string MAX_DESTRUCTION_RESPONSE_INDEX_NAME = "HugeDestructible";
89  static const int TOTAL_DESTRUCTION_MAX_HEALTH_MULTIPLIER = 10;
90 
91  //------------------------------------------------------------------------------------------------
93  {
95  if (prefabData)
96  return prefabData.GetDamageMultiplier(type);
97 
98  return 0;
99  }
100 
101  //------------------------------------------------------------------------------------------------
103  {
105  if (prefabData)
106  return prefabData.GetDamageReduction();
107 
108  return 0;
109  }
110 
111  //------------------------------------------------------------------------------------------------
113  {
115  if (prefabData)
116  return prefabData.GetDamageThreshold();
117 
118  return 0;
119  }
120 
121  //------------------------------------------------------------------------------------------------
122  float GetMaxHealth()
123  {
125  if (!prefabData)
126  return 0;
127 
128  return prefabData.GetMaxHealth();
129  }
130 
131  //------------------------------------------------------------------------------------------------
133  {
134  if (Replication.IsClient())
135  return;
136 
137  SCR_AIWorld aiWorld = SCR_AIWorld.Cast(GetGame().GetAIWorld());
138 
139  if (!aiWorld)
140  return;
141 
142  array<ref Tuple2<vector, vector>> areas = {};
143  array<bool> redoAreas = {};
144  aiWorld.GetNavmeshRebuildAreas(this, areas, redoAreas); // Get area with current phase
145  GetGame().GetCallqueue().CallLater(aiWorld.RequestNavmeshRebuildAreas, 1000, false, areas, redoAreas); // Rebuild later with new phase/when this object is destroyed
146  }
147 
148  //------------------------------------------------------------------------------------------------
149  override void OnDamage(int previousState, int newState, EDamageType type, float damageTaken, float currentHealth, inout vector hitTransform[3], ScriptBitWriter frameData)
150  {
151  UpdateResponseIndex(currentHealth);
152  if (!frameData)
153  return;
154 
155  // Handling double damage in one frame:
156  // FrameData exists from previous damage - the object was damaged twice in a frame, the second damage didn't actually change the phase,
157  // therefore not writing it into the destructionData.
158  if (previousState == newState)
159  return;
160 
161  float maxHealth = GetMaxHealth();
162 
163  SCR_DestructionData destructionData = new SCR_DestructionData();
164  destructionData.m_vHitPosition = hitTransform[0];
165  destructionData.m_vHitDirection = hitTransform[1].Normalized();
166  destructionData.m_vHitNormal = hitTransform[2].Normalized();
167  destructionData.m_fHitDamage = damageTaken;
168  destructionData.m_eDamageType = type;
169  destructionData.m_bTotalDestruction = damageTaken > maxHealth * TOTAL_DESTRUCTION_MAX_HEALTH_MULTIPLIER;
170  destructionData.m_iPreviousPhase = previousState;
171  destructionData.Save(frameData);
172  }
173 
174  //------------------------------------------------------------------------------------------------
176  void SetModel(ResourceName model)
177  {
178  Resource resource = Resource.Load(model);
179  VObject asset;
180  if (resource)
181  {
182  BaseResourceObject resourceObject = resource.GetResource();
183  if (resourceObject)
184  asset = resourceObject.ToVObject();
185  else
186  asset = null;
187  }
188  else
189  asset = null;
190 
191  SetObject(asset, "");
192  Update();
193 
194  Physics phys = GetPhysics();
195  if (!phys)
196  return;
197 
198  // Update physics geometries after mesh change
199  if (!phys.UpdateGeometries())
200  phys.Destroy(); // No geoms found, destroy physics
201  }
202 
203  //------------------------------------------------------------------------------------------------
205  protected void SpawnPhaseObjects(SCR_BaseDestructionPhase phase, SCR_DestructionData destructionData)
206  {
207  Physics physics = GetPhysics();
208 
209  array<ref SCR_BaseSpawnable> destroySpawnObjects = phase.m_aPhaseDestroySpawnObjects;
210  int numSpawnOnDestroy = destroySpawnObjects.Count();
211  for (int i = 0; i < numSpawnOnDestroy; i++)
212  {
213  SCR_BaseSpawnable spawnObject = destroySpawnObjects[i];
214  SCR_DestructionHitInfo hitInfo = SCR_DestructionHitInfo.FromDestructionData(destructionData);
215  spawnObject.Spawn(this, physics, hitInfo);
216  }
217  }
218 
219  //------------------------------------------------------------------------------------------------
221  protected void GoToDamagePhase(int damagePhaseIndex, int previousDamagePhaseIndex, SCR_DestructionData destructionData, bool streamed)
222  {
224  if (!prefabData)
225  return;
226 
227  SCR_BaseDestructionPhase phase = SCR_BaseDestructionPhase.Cast(prefabData.GetDestructionPhase(damagePhaseIndex));
228  if (!phase)
229  return; // Should never happen, unless some memory issue happens
230 
232  if (streamed)
233  {
234  // When streamed, we don't care about sounds or effects, we just change the model and quit
235  SetModel(phase.m_sPhaseModel);
236  SetDamagePhaseSignal(damagePhaseIndex);
237  return;
238  }
239 
240  bool destroyAtNoHealth;
241  prefabData.GetPrefab().Get("DestroyAtNoHealth", destroyAtNoHealth);
242  PlaySound(damagePhaseIndex);
243  SetDamagePhaseSignal(damagePhaseIndex);
244  SetModel(phase.m_sPhaseModel);
245 
246  if (damagePhaseIndex == prefabData.GetNumDestructionPhases() - 1 && destroyAtNoHealth) // is last phase and gets deleted
247  {
248  SetModel(phase.m_sPhaseModel);
249  SpawnPhaseObjects(phase, destructionData);
250  return;
251  }
252 
253  SCR_BaseDestructionPhase previousPhase = SCR_BaseDestructionPhase.Cast(prefabData.GetDestructionPhase(previousDamagePhaseIndex));
254  if (!previousPhase)
255  return; // Should never happen, unless some memory issue happens
256 
257  SpawnPhaseObjects(previousPhase, destructionData);
258 
259  //GetPhaseData(destructibleState);
260  //Change model to new phase
261  //Spawn previous phases effects
262  }
263 
264  //------------------------------------------------------------------------------------------------
266  {
267  vector mins, maxs
268  GetBounds(mins, maxs);
269  return (maxs[0] - mins[0]) * (maxs[1] - mins[1]);
270  }
271 
272  //------------------------------------------------------------------------------------------------
273  void PlaySound(int damagePhaseIndex)
274  {
275  SCR_SoundManagerEntity soundManagerEntity = GetGame().GetSoundManagerEntity();
276  if (!soundManagerEntity)
277  return;
278 
280  if (!prefabData || prefabData.m_eMaterialSoundType == 0)
281  return;
282 
283  SCR_MPDestructionManager destructionManager = SCR_MPDestructionManager.GetInstance();
284  if (!destructionManager)
285  return;
286 
287  SCR_AudioSourceConfiguration audioSourceConfiguration = destructionManager.GetAudioSourceConfiguration();
288  if (!audioSourceConfiguration)
289  return;
290 
291  // Get AudioSource
292  audioSourceConfiguration.m_sSoundEventName = SCR_SoundEvent.SOUND_MPD_ + typename.EnumToString(SCR_EMaterialSoundTypeBreak, prefabData.m_eMaterialSoundType);
293 
294  SCR_AudioSource audioSource = soundManagerEntity.CreateAudioSource(this, audioSourceConfiguration);
295  if (!audioSource)
296  return;
297 
298  // Set signals
299  audioSource.SetSignalValue(SCR_AudioSource.PHASES_TO_DESTROYED_PHASE_SIGNAL_NAME, prefabData.GetNumDestructionPhases() - damagePhaseIndex - 1);
300  audioSource.SetSignalValue(SCR_AudioSource.ENTITY_SIZE_SIGNAL_NAME, GetDestructibleSize());
301 
302  // Set position
303  vector mat[4];
304  GetTransform(mat);
305 
306  // Get center of bounding box
307  vector mins;
308  vector maxs;
309  GetWorldBounds(mins, maxs);
310  mat[3] = vector.Lerp(mins, maxs, 0.5);
311 
312  // Play sound
313  soundManagerEntity.PlayAudioSource(audioSource, mat);
314  }
315 
316  //------------------------------------------------------------------------------------------------
317  // Update DamagePhase signal on entity that has SignalsManagerComponent
318  // Used for triggering looped sounds
319  protected void SetDamagePhaseSignal(int damagePhaseIndex = 0)
320  {
321  SignalsManagerComponent signalsManagerComponent = SignalsManagerComponent.Cast(FindComponent(SignalsManagerComponent));
322  if (!signalsManagerComponent)
323  return;
324 
325  // Pristine = 0, Destoyed > 0
326  signalsManagerComponent.SetSignalValue(signalsManagerComponent.AddOrFindSignal(DAMAGE_PHASE_SIGNAL_NAME), damagePhaseIndex);
327  }
328 
329  //------------------------------------------------------------------------------------------------
330  override void OnStateChanged(int destructibleState, ScriptBitReader frameData, bool JIP)
331  {
332  SCR_DestructionData destructionData = new SCR_DestructionData();
333  if (frameData)
334  destructionData.Load(frameData);
335 
336  GoToDamagePhase(destructibleState, destructionData.m_iPreviousPhase, destructionData, JIP);
337  }
338 
339  //------------------------------------------------------------------------------------------------
340  override void OnBeforeDestroyed()
341  {
343  }
344 
345  //------------------------------------------------------------------------------------------------
346  float CalculateMomentum(Contact contact, float ownerMass, float otherMass)
347  {
348  float DotMultiplier = vector.Dot(contact.VelocityAfter1.Normalized(), contact.VelocityBefore1.Normalized());
349  float MomentumBefore = ownerMass * contact.VelocityBefore1.Length() * SIMULATION_IMPRECISION_MULTIPLIER;
350  float MomentumAfter = ownerMass * contact.VelocityAfter1.Length() * DotMultiplier;
351  float momentumA = Math.AbsFloat(MomentumBefore - MomentumAfter);
352 
353  DotMultiplier = vector.Dot(contact.VelocityAfter2.Normalized(), contact.VelocityBefore2.Normalized());
354  MomentumBefore = otherMass * contact.VelocityBefore2.Length() * SIMULATION_IMPRECISION_MULTIPLIER;
355  MomentumAfter = otherMass * contact.VelocityAfter2.Length() * DotMultiplier;
356  float momentumB = Math.AbsFloat(MomentumBefore - MomentumAfter);
357  return momentumA + momentumB;
358  }
359 
360  //------------------------------------------------------------------------------------------------
361  //Called from OnDamage()
362  void UpdateResponseIndex(float currentHealth)
363  {
364  Physics physics = GetPhysics();
365  if (!physics)
366  return;
367 
368  int responseIndex = physics.GetResponseIndex();
369  if (responseIndex <= MIN_DESTRUCTION_RESPONSE_INDEX)
370  return; // Cannot go lower
371 
372  float healthPercentage = currentHealth / GetMaxHealth();
373  int minMaxDiff = MAX_DESTRUCTION_RESPONSE_INDEX - MIN_DESTRUCTION_RESPONSE_INDEX + 1;
374 
375  responseIndex = Math.ClampInt(MIN_DESTRUCTION_RESPONSE_INDEX - 1 + Math.Ceil(minMaxDiff * healthPercentage), MIN_DESTRUCTION_RESPONSE_INDEX, responseIndex);
376  physics.SetResponseIndex(responseIndex);
377  }
378 
379  //------------------------------------------------------------------------------------------------
380  bool IsProxy()
381  {
382  // GetDestructionManager();
383  // GetItsRplComponent();
384  // Return its Proxy check
385  return true;
386  }
387 
388  //------------------------------------------------------------------------------------------------
390  override void EOnContact(IEntity owner, IEntity other, Contact contact)
391  {
392  if (other && other.IsInherited(SCR_DebrisSmallEntity)) // Ignore impacts from debris
393  return;
394 
396  if (!prefabData)
397  return;
398 
399  // Get the physics of the dynamic object (if owner is static, then we use the other object instead)
400  Physics physics = contact.Physics1;
401 
402  int responseIndex = physics.GetResponseIndex();
403  float ownerMass = physics.GetMass();
404  float otherMass;
405  int ownerReponseIndex = physics.GetResponseIndex();
406  int otherResponseIndex;
407  if (!physics.IsDynamic())
408  {
409  physics = contact.Physics2;
410  if (!physics)
411  return; // This only happens with ragdolls, other objects do have physics here, as collision only happens between physical objects
412 
413  otherMass = physics.GetMass();
414  otherResponseIndex = physics.GetResponseIndex();
415  }
416  else
417  {
418  Physics otherPhysics = other.GetPhysics();
419  if (!otherPhysics)
420  return; // This only happens with ragdolls, other objects do have physics here, as collision only happens between physical objects
421 
422  otherMass = otherPhysics.GetMass();
423  otherResponseIndex = otherPhysics.GetResponseIndex();
424  }
425 
426  float momentum = CalculateMomentum(contact, ownerMass, otherMass);
427 
428  // Now get the relative force, which is the impulse divided by the mass of the dynamic object
429  float relativeForce = 0;
430  if (physics.IsDynamic())
431  {
432  relativeForce = contact.Impulse / physics.GetMass();
433  //We divide the relative force by 20, because for some reason, objects with this response index receive ~20x bigger impulse
434  //TODO: investigate @matousvoj1
435  if (responseIndex >= MIN_DESTRUCTION_RESPONSE_INDEX && responseIndex <= MAX_DESTRUCTION_RESPONSE_INDEX)
436  relativeForce *= 0.05;
437  }
438 
439  /*if (relativeForce < componentData.m_fRelativeContactForceThresholdMinimum) // Below minimum threshold, ignore
440  return;*/
441 
442  vector outMat[3];
443  vector relVel = contact.VelocityBefore2 - contact.VelocityBefore1;
444  outMat[0] = contact.Position; // Hit position
445  outMat[1] = relVel.Normalized(); // Hit direction
446  outMat[2] = contact.Normal; // Hit normal
447 
448  //If vehicle response index is the same or higher -> automatically destroy
449  if (otherResponseIndex - MIN_MOMENTUM_RESPONSE_INDEX >= ownerReponseIndex - MIN_DESTRUCTION_RESPONSE_INDEX)
450  {
451  HandleDamage(EDamageType.TRUE, prefabData.GetMaxHealth(), outMat); //Immediately destroy, otherwise the other object goes right through
452  }
453  else
454  {
455  //Vehicle response index is smaller -> just deal damage, vehicle gets stopped
456  // Send damage to damage handling
457  HandleDamage(EDamageType.COLLISION, momentum * prefabData.m_fMomentumToDamageScale, outMat);
458  }
459 
460  return;
461  }
462 
463  //------------------------------------------------------------------------------------------------
464  void SCR_DestructibleEntity(IEntitySource src, IEntity parent)
465  {
466  SetEventMask(EntityEvent.CONTACT);
467  }
468 };
SCR_AudioSource
Definition: SCR_AudioSource.c:1
DestructibleEntityClass
Definition: DestructibleEntityClass.c:12
GetPrefabData
SCR_VehicleDamageManagerComponentClass GetPrefabData()
Definition: SCR_VehicleDamageManagerComponent.c:260
GetMaxHealth
float GetMaxHealth()
Definition: SCR_DestructibleEntity.c:122
EntityEditorProps
enum EQueryType EntityEditorProps(category:"GameScripted/Sound", description:"THIS IS THE SCRIPT DESCRIPTION.", color:"0 0 255 255")
Definition: SCR_AmbientSoundsComponent.c:12
PlaySound
void PlaySound(int damagePhaseIndex)
Definition: SCR_DestructibleEntity.c:273
CalculateMomentum
float CalculateMomentum(Contact contact, float ownerMass, float otherMass)
Definition: SCR_DestructibleEntity.c:346
IsProxy
bool IsProxy()
Definition: SCR_DestructibleEntity.c:380
SCR_BaseDestructionPhase
Definition: SCR_BaseDestructionPhase.c:1
GetGame
ArmaReforgerScripted GetGame()
Definition: game.c:1424
OnBeforeDestroyed
override void OnBeforeDestroyed()
Definition: SCR_DestructibleEntity.c:340
SCR_SoundEvent
Definition: SCR_SoundEvent.c:1
RegenerateNavmeshDelayed
void RegenerateNavmeshDelayed()
Definition: SCR_DestructibleEntity.c:132
SCR_DestructibleEntity
void SCR_DestructibleEntity(IEntitySource src, IEntity parent)
Definition: SCR_DestructibleEntity.c:464
UpdateResponseIndex
void UpdateResponseIndex(float currentHealth)
Definition: SCR_DestructibleEntity.c:362
SCR_SoundManagerEntity
Definition: SCR_SoundManagerEntity.c:17
SIMULATION_IMPRECISION_MULTIPLIER
SCR_DestructionData SIMULATION_IMPRECISION_MULTIPLIER
DestructibleEntity
Definition: DestructibleEntity.c:12
GoToDamagePhase
protected void GoToDamagePhase(int damagePhaseIndex, int previousDamagePhaseIndex, SCR_DestructionData destructionData, bool streamed)
Only call from OnStateChanged, otherwise you have HUGE desync.
Definition: SCR_DestructibleEntity.c:221
SCR_AIWorld
Definition: SCR_AIWorld.c:23
SetDamagePhaseSignal
protected void SetDamagePhaseSignal(int damagePhaseIndex=0)
Definition: SCR_DestructibleEntity.c:319
GetDestructibleSize
float GetDestructibleSize()
Definition: SCR_DestructibleEntity.c:265
GetDamageThreshold
float GetDamageThreshold()
Definition: SCR_DestructibleEntity.c:112
m_vHitNormal
vector m_vHitNormal
Definition: SCR_MeleeComponent.c:16
BaseDestructibleData
Definition: BaseDestructibleData.c:12
Attribute
typedef Attribute
Post-process effect of scripted camera.
SCR_MPDestructionManager
Definition: SCR_MPDestructionManager.c:103
Update
override void Update(float timeSlice)
Definition: SCR_CampaignBuildingGadgetToolComponent.c:28
SCR_DebrisSmallEntity
Definition: DebrisSmallEntity.c:14
OnStateChanged
override void OnStateChanged(int destructibleState, ScriptBitReader frameData, bool JIP)
Definition: SCR_DestructibleEntity.c:330
OnDamage
override void OnDamage(int previousState, int newState, EDamageType type, float damageTaken, float currentHealth, inout vector hitTransform[3], ScriptBitWriter frameData)
Definition: SCR_DestructibleEntity.c:149
m_vHitDirection
vector m_vHitDirection
Definition: SCR_MeleeComponent.c:15
EOnContact
override void EOnContact(IEntity owner, IEntity other, Contact contact)
Contact.
Definition: SCR_DestructibleEntity.c:390
type
EDamageType type
Definition: SCR_DestructibleTreeV2.c:32
EDamageType
EDamageType
Definition: EDamageType.c:12
SetModel
void SetModel(ResourceName model)
Sets the model of the object.
Definition: SCR_DestructibleEntity.c:176
m_vHitPosition
vector m_vHitPosition
Definition: SCR_MeleeComponent.c:14
SCR_DestructibleEntityClass
Definition: SCR_DestructibleEntity.c:3
SCR_DestructionData
Definition: SCR_DestructibleEntity.c:16
GetDamageReduction
float GetDamageReduction()
Definition: SCR_DestructibleEntity.c:102
GetDamageMultiplier
float GetDamageMultiplier(EDamageType type)
Definition: SCR_DestructibleEntity.c:92
category
params category
Definition: SCR_VehicleDamageManagerComponent.c:180
SpawnPhaseObjects
protected void SpawnPhaseObjects(SCR_BaseDestructionPhase phase, SCR_DestructionData destructionData)
Spawns objects that are meant to be created when the object is destroyed (particles,...
Definition: SCR_DestructibleEntity.c:205