Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_DestructionDamageManagerComponent.c
Go to the documentation of this file.
1#define ENABLE_BASE_DESTRUCTION
3{
4 [Attribute("100", UIWidgets.Slider, "Base health value of the object. Damage received above this value results in destruction (overrides HPMax in hit zone)", "0.01 100000 0.01", category: "Destruction Setup")]
5 float m_fBaseHealth;
6 [Attribute("50", UIWidgets.Slider, "Relative contact force to damage multiplier", "0.01 50000 0.01", category: "Destruction Setup")]
7 float m_fForceToDamageScale;
8 [Attribute("0.05", UIWidgets.Slider, "Contact momentum to damage multiplier", "0.01 10 0.01", category: "Destruction Setup")]
9 float m_fMomentumToDamageScale;
10 [Attribute("0.5", UIWidgets.Slider, "Minimum relative contact force (impulse / mass) threshold below which contacts are ignored", "0 50000 0.01", category: "Destruction Setup")]
11 float m_fRelativeContactForceThresholdMinimum;
12 [Attribute("3000", UIWidgets.Slider, "Maximum damage threshold above which the object is completely destroyed and no effects are played (eg: nuclear bomb damage)", "0.01 50000 0.01", category: "Destruction Setup")]
13 float m_fDamageThresholdMaximum;
14 [Attribute("0", desc: "Should children destructible objects also receive the damage dealt to this one?", category: "Destruction Setup")]
15 bool m_bPassDamageToChildren;
16 [Attribute("0", desc: "Should the parent of this object also be destroyed when this object gets destroyed?", category: "Destruction Setup")]
17 bool m_bDestroyParentWhenDestroyed;
18 [Attribute("0", desc: "Should the children of this object also be destroyed when this object gets destroyed?", category: "Destruction Setup")]
19 bool m_bDestroyChildrenWhenDestroyed;
20
21 [Attribute("", UIWidgets.Object, "List of objects (particles, debris, etc) to spawn on destruction of the object", category: "Destruction FX")]
22 ref array<ref SCR_BaseSpawnable> m_DestroySpawnObjects;
23}
24
26class SCR_DestructionDamageManagerComponent : SCR_DamageManagerComponent
27{
28#ifdef ENABLE_BASE_DESTRUCTION
30 protected static bool s_bReadingInit = false; //Used to determine whether we are in gameplay or synchronization state
31
32#ifdef WORKBENCH
33 protected static bool s_bPrintMissingComponent;
34 protected static bool s_bPrintMissingPlayerController;
35 protected static bool s_bPrintInitializationFailed;
36#endif
37
38 protected static int s_iFirstFreeDestructionBaseData = -1;
39 protected static ref array<ref SCR_DestructionBaseData> s_aDestructionBaseData = {};
40
41 protected int m_iDestructionBaseDataIndex = -1;
42 protected int m_iLastCollisionTime = -1;
43
44 //------------------------------------------------------------------------------------------------
45 static bool GetReadingInit(bool readingInit)
46 {
47 return s_bReadingInit;
48 }
49
50 //------------------------------------------------------------------------------------------------
51 static void SetReadingInit(bool readingInit)
52 {
53 s_bReadingInit = readingInit;
54 }
55
56 //------------------------------------------------------------------------------------------------
58 {
60 m_iDestructionBaseDataIndex = AllocateDestructionBaseData();
61
62 return s_aDestructionBaseData[m_iDestructionBaseDataIndex];
63 }
64
65 //------------------------------------------------------------------------------------------------
66 private int AllocateDestructionBaseData()
67 {
68 if (s_iFirstFreeDestructionBaseData == -1)
69 return s_aDestructionBaseData.Insert(new SCR_DestructionBaseData());
70 else
71 {
72 int returnIndex = s_iFirstFreeDestructionBaseData;
73 SCR_DestructionBaseData data = s_aDestructionBaseData[returnIndex];
74 s_iFirstFreeDestructionBaseData = data.m_iNextFreeIndex;
75 data.m_iNextFreeIndex = -1;
76 return returnIndex;
77 }
78 }
79
80 //------------------------------------------------------------------------------------------------
81 private void FreeDestructionBaseData(int index)
82 {
83 s_aDestructionBaseData[index].Reset();
84 s_aDestructionBaseData[index].m_iNextFreeIndex = s_iFirstFreeDestructionBaseData;
85 s_iFirstFreeDestructionBaseData = index;
86 }
87
88 //------------------------------------------------------------------------------------------------
90 static ScriptInvoker GetOnDestructibleDestroyedInvoker()
91 {
94
96 }
97
98 //------------------------------------------------------------------------------------------------
100 ScriptInvoker GetOnDamageInvoker()
101 {
103 }
104
105 //------------------------------------------------------------------------------------------------
106 SCR_DestructionHitInfo GetDestructionHitInfo(bool createNew = false)
107 {
108 if (m_iDestructionBaseDataIndex == -1 && !createNew)
109 return null;
110
111 return GetDestructionBaseData().GetHitInfo(createNew);
112 }
113
114 //------------------------------------------------------------------------------------------------
115 bool ShouldDestroyParent()
116 {
117 SCR_DestructionDamageManagerComponentClass prefabData = SCR_DestructionDamageManagerComponentClass.Cast(GetComponentData(GetOwner()));
118 return prefabData && prefabData.m_bDestroyParentWhenDestroyed;
119 }
120
121 //------------------------------------------------------------------------------------------------
122 bool ShouldDestroyChildren()
123 {
124 SCR_DestructionDamageManagerComponentClass prefabData = SCR_DestructionDamageManagerComponentClass.Cast(GetComponentData(GetOwner()));
125 return prefabData && prefabData.m_bDestroyChildrenWhenDestroyed;
126 }
127
128 //------------------------------------------------------------------------------------------------
130 bool GetDisablePhysicsOnDestroy()
131 {
132 return true;
133 }
134
135 //------------------------------------------------------------------------------------------------
137 bool GetUndamaged()
138 {
140 return GetHealth() == 1;
141
142 return !GetDestructionBaseData().GetHitInfo(false) && GetHealth() == 1;
143 }
144
145 //------------------------------------------------------------------------------------------------
147 bool GetDestroyed()
148 {
150 return GetHealth() <= 0;
151
152 return GetHealth() <= 0;
153 }
154
155 //------------------------------------------------------------------------------------------------
157 [RplRpc(RplChannel.Unreliable, RplRcver.Broadcast)]
158 void RPC_DoSpawnAllDestroyEffects()
159 {
160 SCR_DestructionDamageManagerComponentClass componentData = SCR_DestructionDamageManagerComponentClass.Cast(GetComponentData(GetOwner()));
161 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, new SCR_DestructionHitInfo());
162
163 IEntity child = GetOwner().GetChildren();
164 while (child)
165 {
166 SCR_DestructionDamageManagerComponent destructible = SCR_DestructionDamageManagerComponent.Cast(child.FindComponent(SCR_DestructionDamageManagerComponent));
167 if (!destructible)
168 {
169 child = child.GetSibling();
170 continue;
171 }
172
173 SCR_DestructionDamageManagerComponentClass componentDataChild = SCR_DestructionDamageManagerComponentClass.Cast(destructible.GetComponentData(child));
174 SCR_DestructionUtility.SpawnDestroyObjects(child, componentDataChild.m_DestroySpawnObjects, new SCR_DestructionHitInfo());
175
176 child = child.GetSibling();
177 }
178 }
179
180 //------------------------------------------------------------------------------------------------
181 void DeleteParentWithEffects()
182 {
183 if (GetDestructionBaseData().GetDestructionQueued())
184 return; // Destruction queued already, don't do this.
185
186 RPC_DoSpawnAllDestroyEffects();
187 Rpc(RPC_DoSpawnAllDestroyEffects);
188
189 DeleteDestructibleDelayed();
190 }
191
192 //------------------------------------------------------------------------------------------------
194 void DeleteDestructibleDelayed()
195 {
196 if (GetOwner().IsDeleted())
197 return;
198
199 if (GetDestructionBaseData().GetDestructionQueued())
200 return; // Destruction queued already, don't do this.
201
203 GetGame().GetCallqueue().CallLater(DeleteDestructible);
204 }
205
206 //------------------------------------------------------------------------------------------------
208 void DeleteDestructibleChildrenDelayed(bool withEffects = true)
209 {
210 if (GetOwner().IsDeleted())
211 return;
212
213 GetGame().GetCallqueue().CallLater(DeleteDestructibleChildren, param1: withEffects);
214 }
215
216 //------------------------------------------------------------------------------------------------
219 private void DeleteDestructible()
220 {
221 if (GetOwner().IsDeleted())
222 return;
223
224 if (ShouldDestroyParent())
225 {
226 IEntity parent = GetOwner().GetParent();
227 if (parent)
228 {
229 SCR_DestructionDamageManagerComponent destructible = SCR_DestructionDamageManagerComponent.Cast(parent.FindComponent(SCR_DestructionDamageManagerComponent));
230 if (destructible)
231 {
232 destructible.DeleteParentWithEffects();
233 return;
234 }
235 }
236 }
237
238 if (ShouldDestroyChildren())
239 DeleteDestructibleChildren();
240
241 SCR_DestructionUtility.RegenerateNavmeshDelayed(GetOwner());
242
243 array<HitZone> outHitZones = {};
244 if (GetAllHitZones(outHitZones) < 1)
245 RplComponent.DeleteRplEntity(GetOwner(), false);
246
247 SCR_DestructibleHitzone destructibleHitZone;
248 foreach (HitZone hitZone : outHitZones)
249 {
250 destructibleHitZone = SCR_DestructibleHitzone.Cast(hitZone);
251 if (!destructibleHitZone)
252 continue;
253
254 if (destructibleHitZone.GetDestructionHandler())
255 return;
256 }
257
258 RplComponent.DeleteRplEntity(GetOwner(), false);
259 }
260
261 //------------------------------------------------------------------------------------------------
264 private void DeleteDestructibleChildren(bool withEffects = true)
265 {
266 IEntity child = GetOwner().GetChildren();
267 // Iterate through all siblings
268 SCR_DestructionDamageManagerComponent childDestructionComponent;
269 while (child)
270 {
271 // Store the next sibling before potentially destroying the current one
272 IEntity nextSibling = child.GetSibling();
273
274 // Check if the child has a destruction component
275 childDestructionComponent = SCR_DestructionDamageManagerComponent.Cast(child.FindComponent(SCR_DestructionDamageManagerComponent));
276
277 if (childDestructionComponent)
278 {
279 if (withEffects)
280 // If it has a destruction component, use it to destroy the child
281 childDestructionComponent.DeleteParentWithEffects();
282 else
283 childDestructionComponent.DeleteDestructibleDelayed();
284 }
285
286 // Move to the next sibling
287 child = nextSibling;
288 }
289 }
290
291 //------------------------------------------------------------------------------------------------
293 void SetHitZoneHealth(float baseHealth, bool clearDamage = true)
294 {
295 HitZone hitZone = GetDefaultHitZone();
296 hitZone.SetMaxHealth(baseHealth);
297 hitZone.SetHealth(baseHealth);
298 }
299
300 //------------------------------------------------------------------------------------------------
302 void SetHitZoneDamage(float damage)
303 {
304 HitZone hitZone = GetDefaultHitZone();
305 hitZone.SetHealth(hitZone.GetMaxHealth() - damage);
306 }
307
308 //------------------------------------------------------------------------------------------------
310 SCR_DestructionHitInfo CreateDestructionHitInfo(bool totalDestruction, float lastHealth, float hitDamage, EDamageType damageType, vector hitPosition, vector hitDirection, vector hitNormal)
311 {
312 SCR_DestructionHitInfo hitInfo = GetDestructionBaseData().GetHitInfo();
313
314 hitInfo.m_TotalDestruction = totalDestruction;
315 hitInfo.m_LastHealth = lastHealth;
316 hitInfo.m_HitDamage = hitDamage;
317 hitInfo.m_DamageType = damageType;
318 hitInfo.m_HitPosition = hitPosition;
319 hitInfo.m_HitDirection = hitDirection;
320 hitInfo.m_HitNormal = hitNormal;
321
322 return hitInfo;
323 }
324
325 //------------------------------------------------------------------------------------------------
327 bool GetCanBeDamaged()
328 {
329 return true;
330 }
331
332 //------------------------------------------------------------------------------------------------
334 void QueueDestroy()
335 {
336 }
337
338 //------------------------------------------------------------------------------------------------
340 void HandleDestruction()
341 {
342 if (GetOwner().IsDeleted())
343 return;
344
345 SCR_DestructionDamageManagerComponentClass componentData = SCR_DestructionDamageManagerComponentClass.Cast(GetComponentData(GetOwner()));
346 if (!componentData)
347 return;
348
349 SCR_DestructionUtility.SpawnDestroyObjects(GetOwner(), componentData.m_DestroySpawnObjects, GetDestructionHitInfo());
350 DeleteDestructibleDelayed();
351 }
352
353 //------------------------------------------------------------------------------------------------
355 void InitDestruction()
356 {
357 }
358
359 //------------------------------------------------------------------------------------------------
361 void NetReceiveHitData(int hitIndex, EDamageType damageType, float damage, vector hitPosition, vector hitDirection)
362 {
363 }
364
365 //------------------------------------------------------------------------------------------------
367 void NetReadInit(ScriptBitReader reader)
368 {
369 }
370
371 //------------------------------------------------------------------------------------------------
373 void NetWriteInit(ScriptBitWriter writer)
374 {
375 }
376
377 //------------------------------------------------------------------------------------------------
378 void ReplicateDestructibleState(int damagePhase = 0, bool silent = false)
379 {
380 }
381
382 //------------------------------------------------------------------------------------------------
384 [RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
385 private void RPC_QueueDestroy(bool totalDestruction, float lastHealth, float hitDamage, EDamageType damageType, vector hitPosition, vector hitDirection, vector hitNormal)
386 {
387 SetHitZoneDamage(GetMaxHealth());
388 CreateDestructionHitInfo(totalDestruction, lastHealth, hitDamage, damageType, hitPosition, hitDirection, hitNormal);
389 QueueDestroy();
390
391 HandleDestruction();
392 }
393
394 //------------------------------------------------------------------------------------------------
395 protected RplComponent FindParentRplComponent()
396 {
397 IEntity parent = GetOwner().GetParent();
398 RplComponent currentRplComponent;
399 while (parent)
400 {
401 currentRplComponent = RplComponent.Cast(parent.FindComponent(RplComponent));
402 parent = parent.GetParent();
403 }
404
405 return currentRplComponent;
406 }
407
408 //------------------------------------------------------------------------------------------------
409 protected void PassDamageToChildren(notnull BaseDamageContext damageContext)
410 {
411 IEntity child = GetOwner().GetChildren();
412 while (child)
413 {
414 SCR_DestructionDamageManagerComponent destructionComponent = SCR_DestructionDamageManagerComponent.Cast(child.FindComponent(SCR_DestructionDamageManagerComponent));
415 if (!destructionComponent)
416 {
417 child = child.GetSibling();
418 continue;
419 }
420
421 IEntity currentChild = child;
422 child = child.GetSibling();
423
424 BaseDamageContext childContext = BaseDamageContext.Cast(damageContext.Clone());
425 childContext.hitEntity = currentChild;
426 childContext.struckHitZone = destructionComponent.GetDefaultHitZone();
427
428 destructionComponent.HandleDamage(childContext);
429 }
430 }
431
432 //------------------------------------------------------------------------------------------------
434 bool IsProxy()
435 {
436 RplComponent rplComponent = RplComponent.Cast(GetOwner().FindComponent(RplComponent));
437 return (rplComponent && rplComponent.IsProxy());
438 }
439
440 //------------------------------------------------------------------------------------------------
442 override bool FilterContact(IEntity owner, IEntity other, Contact contact)
443 {
444 if (!other)
445 return false;
446
447 if (!owner.GetPhysics() || !other.GetPhysics())
448 return false;
449
450 if(!contact.Physics2.IsDynamic())
451 return false;
452
453 if (ChimeraCharacter.Cast(other))
454 return false;
455
456 //we call super here, because a) we want to first check for characters as it's cheaper, and b) we need to do other checks after super to not do them unnecessarily
457 if (!super.FilterContact(owner, other, contact))
458 return false;
459
460 //checks that were previously in OnFilteredContact
461 if (GetDestroyed() || !GetCanBeDamaged())
462 return false;
463
464 if (other && other.IsInherited(SCR_DebrisSmallEntity)) // Ignore impacts from debris
465 return false;
466
467 //this is also called in OnFilteredContact to get the actual component data, but we check here as well to avoid calling script in case they dont exist
469 if (!componentData)
470 return false;
471
472 // Now get the relative force, which is the impulse divided by the mass of the dynamic object
473 float relativeForce = contact.Impulse / contact.Physics2.GetMass();
474 if (relativeForce < componentData.m_fRelativeContactForceThresholdMinimum) // Below minimum threshold, ignore
475 return false;
476
477 // Check if enough time has passed since the last collision (1 second = 1000 milliseconds)
478 int currentTime = GetGame().GetWorld().GetWorldTime();
479 if (m_iLastCollisionTime > 0 && currentTime - m_iLastCollisionTime < 1000)
480 return false;
481
482 // Update the last collision time
483 m_iLastCollisionTime = currentTime;
484
485 return true;
486 }
487
488 //------------------------------------------------------------------------------------------------
489 override void OnFilteredContact(IEntity owner, IEntity other, Contact contact)
490 {
491 Physics ownerPhysics = owner.GetPhysics();
492 Physics otherPhysics = other.GetPhysics();
493 int ownerReponseIndex = ownerPhysics.GetResponseIndex();
494 int otherResponseIndex = otherPhysics.GetResponseIndex();
495 float damage = GetMaxHealth();
496 EDamageType damageType = EDamageType.TRUE;
497
498 // If vehicle response index is smaller -> deal damage
499 // Otherwise -> destroy
500 if (ownerReponseIndex < MIN_DESTRUCTION_RESPONSE_INDEX || otherResponseIndex - MIN_MOMENTUM_RESPONSE_INDEX < ownerReponseIndex - MIN_DESTRUCTION_RESPONSE_INDEX)
501 {
503 float momentum = SCR_DestructionUtility.CalculateMomentum(contact, ownerPhysics.GetMass(), otherPhysics.GetMass());
504 damage = momentum * componentData.m_fMomentumToDamageScale;
505 damageType = EDamageType.COLLISION;
506 }
507
508 vector outMat[3];
509 vector relVel = contact.VelocityBefore2 - contact.VelocityBefore1;
510 outMat[0] = contact.Position; // Hit position
511 outMat[1] = relVel.Normalized(); // Hit direction
512 outMat[2] = contact.Normal; // Hit normal
513
514 SCR_DamageContext damageContext = new SCR_DamageContext(damageType, damage, outMat, GetOwner(), null,Instigator.CreateInstigator(other), null, -1, -1);
515 damageContext.damageEffect = new SCR_CollisionDamageEffect();
516 HandleDamage(damageContext);
517 }
518
519 //------------------------------------------------------------------------------------------------
520 override void OnDamage(notnull BaseDamageContext damageContext)
521 {
522 super.OnDamage(damageContext);
523
525 if (!componentData)
526 return;
527
528 if (componentData.m_bPassDamageToChildren)
529 PassDamageToChildren(damageContext);
530
532 {
534 if (onDamage)
535 onDamage.Invoke();
536 }
537
538 if (IsProxy())
539 return;
540
541 Physics physics = GetOwner().GetPhysics();
542 if (physics)
543 {
544 if (!GetDestroyed())
545 {
548 return;
549 }
550
551 if (physics.IsDynamic() && !physics.IsActive()) // Wake the object up if sleeping
552 physics.ApplyImpulse(vector.Up * physics.GetMass() * 0.001); // Applying impulse instead of SetActive, as we don't want to override ActiveState.ALWAYS_ACTIVE if set
553
554 // Should disable physics on destroy
555 if (GetDisablePhysicsOnDestroy())
556 physics.SetInteractionLayer(EPhysicsLayerDefs.VehicleCast);
557 }
558
559 float previousHealth;
561 previousHealth = GetDestructionBaseData().GetPreviousHealth();
562
563 CreateDestructionHitInfo(damageContext.damageValue >= componentData.m_fDamageThresholdMaximum, previousHealth, damageContext.damageValue, damageContext.damageType, damageContext.hitPosition, damageContext.hitDirection, damageContext.hitNormal);
564
565 QueueDestroy();
566
567 SCR_DestructionHitInfo destructionHitInfo = GetDestructionHitInfo();
568 if (!destructionHitInfo)
569 return;
570
571 Rpc(RPC_QueueDestroy, destructionHitInfo.m_TotalDestruction, destructionHitInfo.m_LastHealth, destructionHitInfo.m_HitDamage, destructionHitInfo.m_DamageType, destructionHitInfo.m_HitPosition, destructionHitInfo.m_HitDirection, destructionHitInfo.m_HitNormal);
572
573 if (!IsDestructionQueued())
574 {
575 HandleDestruction();
577 }
578 }
579
580 //------------------------------------------------------------------------------------------------
582 {
584 return data.GetDestructionQueued();
585 }
586
587 //------------------------------------------------------------------------------------------------
589 {
591 if (!componentData)
592 return 3000;
593
594 return componentData.m_fDamageThresholdMaximum;
595 }
596
597 //------------------------------------------------------------------------------------------------
598 override void OnPostInit(IEntity owner)
599 {
600 super.OnPostInit(owner);
601
602 // The default hitzone will always exist in play mode, but while in edit mode in WB it is not guaranteed to exist.
603 // Therefore, we make sure to check if it exists during init.
604 if (!GetDefaultHitZone())
605 return;
606
607 SetEventMask(owner, EntityEvent.CONTACT);
608
610 SetHitZoneHealth(componentData.m_fBaseHealth);
611
612 InitDestruction();
613 }
614
615 //------------------------------------------------------------------------------------------------
617 {
618#ifdef WORKBENCH
619 s_bPrintMissingComponent = false;
620 s_bPrintMissingPlayerController = false;
621 s_bPrintInitializationFailed = false;
622#endif
623
625 FreeDestructionBaseData(m_iDestructionBaseDataIndex);
626 }
627#endif
628}
ArmaReforgerScripted GetGame()
Definition game.c:1398
override bool FilterContact(IEntity owner, IEntity other, Contact contact)
Armor doesn't take collisiondamage.
override bool HandleDamage(BaseDamageContext damageContext, IEntity owner)
SCR_CharacterBloodHitZone OnDamage
Resilience - incapacitation or death, depending on game mode settings.
SCR_CharacterSoundComponentClass GetComponentData()
SCR_DestructibleEntityClass MIN_MOMENTUM_RESPONSE_INDEX
float GetMaxHealth()
SCR_DestructionDamageManagerComponentClass s_OnDestructibleDestroyed
Base destruction component, destruction types extend from this.
notnull SCR_DestructionBaseData GetDestructionBaseData()
void PassDamageToChildren(notnull BaseDamageContext damageContext)
void ~SCR_DestructionDamageManagerComponent()
RplComponent FindParentRplComponent()
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
destructible ReplicateDestructibleState()
SCR_DestructionDamageManagerComponent destructible
Get all prefabs that have the spawner data
float GetHealth()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
override void OnFilteredContact(IEntity owner, IEntity other, Contact contact)
enum EVehicleType IEntity
proto external Managed FindComponent(typename typeName)
proto external Physics GetPhysics()
proto external IEntity GetChildren()
proto external IEntity GetParent()
proto external IEntity GetSibling()
ref SCR_DestructionBaseHandler GetDestructionHandler()
void SetDestructionQueued(bool destructionQueued)
ScriptInvoker GetOnDamage(bool createNew=true)
SCR_DestructionHitInfo GetHitInfo(bool createNew=true)
void SetPreviousHealth(float previousHealth)
static float CalculateMomentum(notnull Contact contact, float ownerMass, float otherMass)
static void UpdateResponseIndex(notnull Physics physics, float health, float maxHealth)
IEntity GetOwner()
Owner entity of the fuel tank.
SCR_FieldOfViewSettings Attribute
EntityEvent
Various entity events.
Definition EntityEvent.c:14
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
Tuple param1
ScriptInvokerBase< func > ScriptInvoker
Definition tools.c:134