Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_DestructionMultiPhaseComponent.c
Go to the documentation of this file.
1[ComponentEditorProps(category: "GameScripted/Destruction", description: "Multi-Phase destruction component, for objects that go through several damage phases")]
3{
4 [Attribute("1", UIWidgets.CheckBox, "If true, the object will be deleted after being damaged beyond the final damage phase", category: "Destruction Multi-Phase")]
5 bool m_bDeleteAfterFinalPhase;
6 [Attribute("", UIWidgets.Object, "List of the individual damage phases (sequential) above initial damage phase", category: "Destruction Multi-Phase")]
7 ref array<ref SCR_DamagePhaseData> m_aDamagePhases;
8
9 [Attribute("0", uiwidget: UIWidgets.ComboBox, "Type of material for destruction sound", "", ParamEnumArray.FromEnum(SCR_EMaterialSoundTypeBreak))]
10 SCR_EMaterialSoundTypeBreak m_eMaterialSoundType;
11
12 [Attribute("0")]
13 float m_fMeshChangeDelay;
14
15 [Attribute()]
16 ref DestructionHeatmapEntry m_EntryData;
17}
18
20class SCR_DestructionMultiPhaseComponent : SCR_DestructionDamageManagerComponent
21{
22#ifdef ENABLE_BASE_DESTRUCTION
23 private static int s_iFirstFreeDestructionMultiPhaseData = -1;
24 private static ref array<ref SCR_DestructionMultiPhaseData> s_aDestructionMultiPhaseData = {};
25
26 private int m_iDestructionMultiPhaseDataIndex = -1;
27
28 protected int m_iOriginalHealth;
29 protected float m_fNextPhaseHealth;
30
31 static const string DAMAGE_PHASE_SIGNAL_NAME = "DamagePhase";
32
33 //------------------------------------------------------------------------------------------------
34 protected notnull SCR_DestructionMultiPhaseData GetDestructionMultiPhaseData()
35 {
36 if (m_iDestructionMultiPhaseDataIndex == -1)
37 m_iDestructionMultiPhaseDataIndex = AllocateDestructionMultiPhaseData();
38
39 return s_aDestructionMultiPhaseData[m_iDestructionMultiPhaseDataIndex];
40 }
41
42 //------------------------------------------------------------------------------------------------
43 private int AllocateDestructionMultiPhaseData()
44 {
45 if (s_iFirstFreeDestructionMultiPhaseData == -1)
46 return s_aDestructionMultiPhaseData.Insert(new SCR_DestructionMultiPhaseData());
47 else
48 {
49 int returnIndex = s_iFirstFreeDestructionMultiPhaseData;
50 SCR_DestructionMultiPhaseData data = s_aDestructionMultiPhaseData[returnIndex];
51 s_iFirstFreeDestructionMultiPhaseData = data.m_iNextFreeIndex;
52 data.m_iNextFreeIndex = -1;
53 return returnIndex;
54 }
55 }
56
57 //------------------------------------------------------------------------------------------------
58 private void FreeDestructionMultiPhaseData(int index)
59 {
60 s_aDestructionMultiPhaseData[index].Reset();
61 s_aDestructionMultiPhaseData[index].m_iNextFreeIndex = s_iFirstFreeDestructionMultiPhaseData;
62 s_iFirstFreeDestructionMultiPhaseData = index;
63 }
64
65 //------------------------------------------------------------------------------------------------
67 SCR_DamagePhaseData GetDamagePhaseData(int damagePhase)
68 {
69 if (damagePhase <= 0 || damagePhase >= GetNumDamagePhases())
70 return null;
71
72 return SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner())).m_aDamagePhases.Get(damagePhase - 1);
73 }
74
75 //------------------------------------------------------------------------------------------------
77 override bool GetDisablePhysicsOnDestroy()
78 {
79 return false;
80 }
81
82 //------------------------------------------------------------------------------------------------
84 bool GetIsInInitialDamagePhase()
85 {
86 return GetDamagePhase() == 0;
87 }
88
89 //------------------------------------------------------------------------------------------------
91 int GetDamagePhase()
92 {
93 if (m_iDestructionMultiPhaseDataIndex == -1)
94 return 0;
95
96 return GetDestructionMultiPhaseData().GetDamagePhase();
97 }
98
99 //------------------------------------------------------------------------------------------------
101 int GetNumDamagePhases()
102 {
103 SCR_DestructionMultiPhaseComponentClass componentData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
104
105 if (componentData.m_aDamagePhases)
106 return componentData.m_aDamagePhases.Count() + 1;
107 else
108 return 1;
109 }
110
111 //------------------------------------------------------------------------------------------------
113 int CalculateDamagePhase(int damagePhase, float health, float damage)
114 {
115 if (damage < 0)
116 return damagePhase;
117
118 while (damage >= 0)
119 {
120 damagePhase++;
121 SCR_DamagePhaseData damagePhaseData = GetDamagePhaseData(damagePhase);
122 if (!damagePhaseData)
123 break;
124
125 damage -= damagePhaseData.m_fPhaseHealth;
126 }
127
128 if (SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner())).m_bDeleteAfterFinalPhase)
129 return Math.ClampInt(damagePhase, 0, GetNumDamagePhases()); // Clamp between initial and 1 past final phase
130 else
131 return Math.ClampInt(damagePhase, 0, GetNumDamagePhases() - 1);
132 }
133
134 //------------------------------------------------------------------------------------------------
135 protected void SetOriginalResourceName(ResourceName originalResourceName)
136 {
137 GetDestructionMultiPhaseData().SetOriginalResourceName(originalResourceName);
138 }
139
140 //------------------------------------------------------------------------------------------------
141 protected ResourceName GetOriginalResourceName()
142 {
143 if (m_iDestructionMultiPhaseDataIndex == -1)
144 return ResourceName.Empty;
145
146 return GetDestructionMultiPhaseData().GetOriginalResourceName();
147 }
148
149 //------------------------------------------------------------------------------------------------
150 protected int GetTargetDamagePhase()
151 {
152 if (m_iDestructionMultiPhaseDataIndex == -1)
153 return 0;
154
155 return GetDestructionMultiPhaseData().GetTargetDamagePhase();
156 }
157
158 //------------------------------------------------------------------------------------------------
159 protected void SetTargetDamagePhase(int targetDamagePhase)
160 {
161 GetDestructionMultiPhaseData().SetTargetDamagePhase(targetDamagePhase);
162 }
163
164 //------------------------------------------------------------------------------------------------
165 protected void SetDamagePhase(int damagePhase)
166 {
167 GetDestructionMultiPhaseData().SetDamagePhase(damagePhase);
168
169 SCR_DestructionUtility.SetDamagePhaseSignal(GetOwner(), damagePhase);
170 }
171
172 //------------------------------------------------------------------------------------------------
174 void GoToDamagePhase(int damagePhase, bool delayMeshChange)
175 {
176 if (damagePhase >= GetNumDamagePhases()) // Going past final phase, so delete if we can
177 {
179 s_OnDestructibleDestroyed.Invoke(this);
180
181 DeleteDestructibleDelayed();
182 return;
183 }
184
185 int currentDamagePhase = GetDamagePhase();
186 if (currentDamagePhase == damagePhase) // Same phase, ignore
187 return;
188
189 SCR_DestructionUtility.RegenerateNavmeshDelayed(GetOwner());
190
191 if (currentDamagePhase == 0)
192 {
193 VObject vObject = GetOwner().GetVObject();
194 if (vObject)
195 SetOriginalResourceName(vObject.GetResourceName());
196 }
197
198 SCR_DestructionMultiPhaseComponentClass componentData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
199 if (!componentData)
200 return;
201
202 float combinedPhaseHealth = componentData.m_fBaseHealth;
203 foreach (int i, SCR_DamagePhaseData phase : componentData.m_aDamagePhases)
204 {
205 if (i + 1 <= damagePhase)
206 combinedPhaseHealth += phase.m_fPhaseHealth;
207 }
208
209 m_fNextPhaseHealth = m_iOriginalHealth - combinedPhaseHealth;
210
211 SetDamagePhase(damagePhase);
212 SetTargetDamagePhase(damagePhase + 1);
213
214 ScriptCallQueue callQueue = GetGame().GetCallqueue();
215 if (!callQueue)
216 return;
217
218 // Cancel any existing model change to ensure the latest phase takes priority
219 float remainingTime = callQueue.GetRemainingTime(ChangeModel);
220 if (remainingTime > -1)
221 callQueue.Remove(ChangeModel);
222
223 int delay;
224 if (delayMeshChange)
225 delay = componentData.m_fMeshChangeDelay;
226
227 SCR_DamagePhaseData data = GetDamagePhaseData(damagePhase);
228 if (!data)
229 return;
230
231 callQueue.CallLater(ChangeModel, delay, param1: data.m_PhaseModel, param2: data.m_bUseMaterialsFromParent);
232 }
233
234 //------------------------------------------------------------------------------------------------
237 protected void ChangeModel(ResourceName modelName, bool useMaterialFromParent = false)
238 {
239 IEntity owner = GetOwner();
240 if (!owner || owner.IsDeleted())
241 return;
242
243 ResourceName modelPath;
244 string remap;
245 SCR_Global.GetModelAndRemapFromResource(modelName, modelPath, remap);
246 if(useMaterialFromParent)
247 {
248 ResourceName modelPathSource;
249 string remapParent;
250 ResourceName prefabPathSource = SCR_ResourceNameUtils.GetPrefabName(owner);
251 SCR_Global.GetModelAndRemapFromResource(prefabPathSource, modelPathSource, remapParent);
252 // Trying to remap slots which are not present in modelPath will result in error in Log Console. This is why we need to sanitize remap string to only contain valid entries.
253 // GetModelAndRemapFromResource will return non empty remap only if prefabPath is linking to actual prefab with remaped materials.
254 // Above behavior can be used to create a prefab, which remaps only 2 certain slots - those will be retrieved by code and used in sanitization process.
255 if (remap)
256 {
257 remap = SCR_DestructionUtility.SanitizeRemapString(remap, remapParent);
258 }
259 else
260 {
261 remap = remapParent;
262 }
263 }
264
265 SCR_DestructionUtility.SetModel(owner, modelPath, remap);
266 }
267
268 //------------------------------------------------------------------------------------------------
270 override bool GetCanBeDamaged()
271 {
272 SCR_DestructionMultiPhaseComponentClass componentData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
273 if (!componentData)
274 return true;
275
276 if (componentData.m_bDeleteAfterFinalPhase)
277 return GetDamagePhase() < GetNumDamagePhases();
278
279 return GetDamagePhase() < GetNumDamagePhases() - 1;
280 }
281
282 //------------------------------------------------------------------------------------------------
284 override void OnDamage(notnull BaseDamageContext damageContext)
285 {
286 if (IsProxy())
287 return;
288
289 if (GetOwner().IsDeleted() || IsDestroyed())
290 return;
291
292 SCR_DestructionMultiPhaseComponentClass componentData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
293
294 float currentHealth = GetHealth();
295 float previousHealth = GetDestructionBaseData().GetPreviousHealth();
296
297 SCR_DestructionHitInfo hitInfo = CreateDestructionHitInfo(damageContext.damageValue >= componentData.m_fDamageThresholdMaximum, previousHealth, damageContext.damageValue, damageContext.damageType, damageContext.hitPosition, damageContext.hitDirection, damageContext.hitNormal);
298
299 if (componentData.m_bPassDamageToChildren)
300 PassDamageToChildren(damageContext);
301
302 if (currentHealth <= 0)
303 {
304 int lastPhase = componentData.m_aDamagePhases.Count();
305
306 if (hitInfo.m_TotalDestruction)
307 {
308 if (!componentData.m_bDeleteAfterFinalPhase && componentData.m_aDamagePhases && !componentData.m_aDamagePhases.IsEmpty() && !(componentData.m_bDestroyChildrenWhenDestroyed && GetOwner().GetParent()))
309 {
310 GoToDamagePhase(lastPhase, false);
311 ReplicateDestructibleState(lastPhase, true);
312
313 // Delete children without spawning effects
314 if (componentData.m_bDestroyChildrenWhenDestroyed)
315 DeleteDestructibleChildrenDelayed(false);
316 }
317 else
318 {
319 //Don't replicate state as there is no particles or sound to be played
320 DeleteDestructibleDelayed();
321 }
322
324 return;
325 }
326
327 if (componentData.m_aDamagePhases && !componentData.m_aDamagePhases.IsEmpty()) // MPD has damage phases
328 {
329 SCR_DamagePhaseData lastPhaseData = GetDamagePhaseData(lastPhase);
330
331 if (componentData.m_eMaterialSoundType > 0)
332 SCR_DestructionUtility.PlaySound(GetOwner(), componentData.m_eMaterialSoundType, GetNumDamagePhases(), GetDamagePhase());
333
334 if (lastPhaseData.m_PhaseDestroySpawnObjects && !lastPhaseData.m_PhaseDestroySpawnObjects.IsEmpty())
335 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), lastPhaseData.m_PhaseDestroySpawnObjects, hitInfo);
336 else
337 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, hitInfo);
338
339 if (componentData.m_bDeleteAfterFinalPhase)
340 {
342 DeleteDestructibleDelayed();
343 }
344 else
345 {
346 GoToDamagePhase(lastPhase, false);
348
349 if (componentData.m_bDestroyChildrenWhenDestroyed)
350 DeleteDestructibleChildrenDelayed();
351 }
352 }
353 else
354 {
355 if (componentData.m_eMaterialSoundType > 0)
356 SCR_DestructionUtility.PlaySound(GetOwner(), componentData.m_eMaterialSoundType, GetNumDamagePhases(), GetDamagePhase());
357
358 if ( !componentData.m_bDestroyParentWhenDestroyed || !GetOwner().GetParent().GetPhysics() )
359 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, hitInfo);
360
362 DeleteDestructibleDelayed();
363 }
364
366 return;
367 }
368
369 int targetDamagePhase = GetTargetDamagePhase();
370 if (targetDamagePhase <= 0)
371 return;
372
373 if (currentHealth <= m_fNextPhaseHealth)
374 {
375 if (componentData.m_eMaterialSoundType > 0)
376 SCR_DestructionUtility.PlaySound(GetOwner(), componentData.m_eMaterialSoundType, GetNumDamagePhases(), GetDamagePhase());
377
378 SCR_DamagePhaseData targetDamagePhaseData = GetDamagePhaseData(targetDamagePhase);
379 if (targetDamagePhaseData.m_PhaseDestroySpawnObjects && !targetDamagePhaseData.m_PhaseDestroySpawnObjects.IsEmpty())
380 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), targetDamagePhaseData.m_PhaseDestroySpawnObjects, hitInfo);
381 else
382 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, hitInfo);
383
384 GoToDamagePhase(targetDamagePhase, true);
385 ReplicateDestructibleState(targetDamagePhase);
386
387 if (componentData.m_bDestroyChildrenWhenDestroyed)
388 DeleteDestructibleChildrenDelayed();
389
390 if (!componentData.m_bDeleteAfterFinalPhase && targetDamagePhase == componentData.m_aDamagePhases.Count()) // Reached last phase
391 {
392 SetHitZoneHealth(0);
393 }
394 }
395
396 if (hitInfo)
398 }
399
400 //------------------------------------------------------------------------------------------------
402 void UpdateResponseIndex()
403 {
404 SCR_DestructionMultiPhaseComponentClass prefabData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
405 if (!prefabData || !prefabData.m_bDeleteAfterFinalPhase)
406 return; // We don't want to drive through indestructible objects
407
408 Physics physics = GetOwner().GetPhysics();
409 if (!physics)
410 return; // No physics, no need to update anything
411
412 if (GetDamagePhase() + 1 < GetNumDamagePhases()) // Not the last phase - don't update the response index
413 return;
414
415 // Is in last phase and will be destroyed after it
416 SCR_DestructionUtility.UpdateResponseIndex(physics, GetHealth(), GetMaxHealth());
417 }
418
419 //------------------------------------------------------------------------------------------------
420 [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
421 protected void RPC_ReplicateMultiPhaseDestructionState(int phase, vector hitPosDirNorm[3], float damage, EDamageType damageType, bool silent)
422 {
423 SCR_DestructionMultiPhaseComponentClass componentData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
424 if (!componentData)
425 return;
426
427 SCR_DestructionHitInfo hitInfo = GetDestructionHitInfo(true);
428 hitInfo.m_HitPosition = hitPosDirNorm[0];
429 hitInfo.m_HitDirection = hitPosDirNorm[1];
430 hitInfo.m_HitNormal = hitPosDirNorm[2];
431 hitInfo.m_HitDamage = damage;
432 hitInfo.m_DamageType = damageType;
433
434 SCR_DamagePhaseData phaseData = GetDamagePhaseData(phase);
435 if (phaseData)
436 {
437 if (!silent)
438 {
439 if (phaseData.m_PhaseDestroySpawnObjects && !phaseData.m_PhaseDestroySpawnObjects.IsEmpty())
440 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), phaseData.m_PhaseDestroySpawnObjects, hitInfo);
441 else
442 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, hitInfo);
443
444 if (componentData.m_eMaterialSoundType > 0)
445 SCR_DestructionUtility.PlaySound(GetOwner(), componentData.m_eMaterialSoundType, GetNumDamagePhases(), GetDamagePhase());
446 }
447
448 GoToDamagePhase(phase, true);
449 }
450 else
451 {
452 if (!silent)
453 {
454 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, hitInfo);
455 if (componentData.m_eMaterialSoundType > 0)
456 SCR_DestructionUtility.PlaySound(GetOwner(), componentData.m_eMaterialSoundType, GetNumDamagePhases(), GetDamagePhase());
457 }
458 }
459 }
460
461 //------------------------------------------------------------------------------------------------
462 override void ReplicateDestructibleState(int damagePhase = 0, bool silent = false)
463 {
464 SCR_DestructionHitInfo hitInfo = GetDestructionHitInfo();
465 if (hitInfo)
466 {
467 vector hitPosDirNorm[3];
468 hitPosDirNorm[0] = hitInfo.m_HitPosition;
469 hitPosDirNorm[1] = hitInfo.m_HitDirection;
470 hitPosDirNorm[2] = hitInfo.m_HitNormal;
471
472 Rpc(RPC_ReplicateMultiPhaseDestructionState, damagePhase, hitPosDirNorm, hitInfo.m_HitDamage, hitInfo.m_DamageType, silent);
473 }
474 }
475
476 //------------------------------------------------------------------------------------------------
477 protected override bool HasDataToReplicate()
478 {
479 if( GetDamagePhase() != 0)
480 return true;
481
482 return false;
483 }
484
485 //------------------------------------------------------------------------------------------------
486 protected override event bool OnRplSave(ScriptBitWriter writer)
487 {
488 super.OnRplSave(writer);
489
490 bool writeDamagePhase = GetDamagePhase() != 0;
491 writer.Write(writeDamagePhase, 1);
492
493 if (writeDamagePhase)
494 writer.Write(GetDamagePhase(), 8); // Write which damage phase we are in
495
496 return true;
497 }
498
499 //------------------------------------------------------------------------------------------------
500 protected override event bool OnRplLoad(ScriptBitReader reader)
501 {
502 super.OnRplLoad(reader);
503
504 bool readDamagePhase;
505 reader.Read(readDamagePhase, 1);
506
507 if (!readDamagePhase)
508 return true;
509
510 int damagePhase;
511 reader.Read(damagePhase, 8); // Read which damage phase we are meant to be in
512
513 if (damagePhase != GetDamagePhase())
514 GoToDamagePhase(damagePhase, false);
515
516 return true;
517 }
518
519 //------------------------------------------------------------------------------------------------
520 override void OnPostInit(IEntity owner)
521 {
522 super.OnPostInit(owner);
523
524 if (IsProxy())
525 return;
526
527 // The default hitzone will always exist in play mode, but while in edit mode in WB it is not guaranteed to exist.
528 // Therefore, we make sure to check if it exists during init.
529 if (!GetDefaultHitZone())
530 return;
531
532 SCR_DestructionMultiPhaseComponentClass componentData = SCR_DestructionMultiPhaseComponentClass.Cast(GetComponentData(GetOwner()));
533 if (!componentData.m_aDamagePhases || componentData.m_aDamagePhases.IsEmpty())
534 return;
535
536 m_iOriginalHealth = componentData.m_fBaseHealth;
537 foreach (SCR_DamagePhaseData damagePhase : componentData.m_aDamagePhases)
538 {
539 m_iOriginalHealth += damagePhase.m_fPhaseHealth;
540 }
541
542 SetHitZoneHealth(m_iOriginalHealth);
543 SetTargetDamagePhase(1);
544 m_fNextPhaseHealth = m_iOriginalHealth - componentData.m_fBaseHealth;
545 }
546#endif
547}
ArmaReforgerScripted GetGame()
Definition game.c:1398
enum EAIGroupCombatMode ComponentEditorProps(category:"GameScripted/AI", description:"Component for utility AI system for groups")
override bool OnRplSave(ScriptBitWriter writer)
override bool OnRplLoad(ScriptBitReader reader)
SCR_CharacterBloodHitZone OnDamage
Resilience - incapacitation or death, depending on game mode settings.
SCR_CharacterSoundComponentClass GetComponentData()
float GetMaxHealth()
void GoToDamagePhase(int damagePhaseIndex, int previousDamagePhaseIndex, SCR_DestructionData destructionData, bool streamed)
Only call from OnStateChanged, otherwise you have HUGE desync.
SCR_DestructionDamageManagerComponentClass s_OnDestructibleDestroyed
Base destruction component, destruction types extend from this.
notnull SCR_DestructionBaseData GetDestructionBaseData()
void PassDamageToChildren(notnull BaseDamageContext damageContext)
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
destructible ReplicateDestructibleState()
override bool IsDestroyed()
Get all prefabs that have the spawner data
Faction GetParent()
override bool HasDataToReplicate()
float GetHealth()
enum EVehicleType IEntity
proto external Physics GetPhysics()
proto external bool IsDeleted()
proto external VObject GetVObject()
Returns visual object set to this Entity. No reference is added.
IEntity GetOwner()
Owner entity of the fuel tank.
SCR_FieldOfViewSettings Attribute
EDamageType
Definition EDamageType.c:13
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
T2 param2
Definition tuple.c:92
Tuple param1