Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_WeaponBlastComponent.c
Go to the documentation of this file.
2{
3 [Attribute(desc: "List effects that will be applied")]
4 protected ref array<ref SCR_WeaponBlastEffect> m_aBlastEffects;
5
6 [Attribute("3.0", desc: "How far will the blast propagate. Also limits propagation sideways.", params: "0.1 inf")]
7 protected float m_fBlastLength;
8
9 [Attribute("30", desc: "How much angle deviation from foward vector of the blast is allowed", params: "0.1 89.9")]
10 protected float m_fBlastConeAngle;
11
12 [Attribute("1", desc: "Determines if game should check if blast will hit something that may cause it to ricochet")]
13 protected bool m_bCanBlastRicochet;
14
15 [Attribute("10", desc: "Determines how strongly ricochet angle of the blast is flattend upon impacting the surface", params: "1 10000")]
17
18 [Attribute("2", desc: "Final amount of damage dealt to destructible entities will be multiplied by this factor", params: "0 inf")]
20
21 [Attribute("0", desc: "Determines if blast shouldnt damage AI characters", category: "AI")]
22 protected bool m_bIgnoreAIUnits;
23
24 [Attribute("1", desc: "Determines if blast that was caused by AI character can damage other characters.", category: "AI")]
26
27 [Attribute("1", desc: "Determines if blast that was caused by AI character can damage things like f.e. wooden fences", category: "AI")]
29
30 [Attribute("0", desc: "Determines if blast that was caused by AI character can damage it when it bouces from f.e. a wall.\nIf m_bAICanBlastCharacters or m_bIgnoreAIUnits is true then this will be considered true", category: "AI")]
31 protected bool m_bAICanDamageItself;
32
33 //------------------------------------------------------------------------------------------------
35 {
36 return m_fBlastLength;
37 }
38
39 //------------------------------------------------------------------------------------------------
41 {
42 return m_fBlastConeAngle;
43 }
44
45 //------------------------------------------------------------------------------------------------
47 {
49 }
50
51 //------------------------------------------------------------------------------------------------
56
57 //------------------------------------------------------------------------------------------------
62
63 //------------------------------------------------------------------------------------------------
65 {
66 return m_bIgnoreAIUnits;
67 }
68
70 {
72 }
73
74 //------------------------------------------------------------------------------------------------
79
80 //------------------------------------------------------------------------------------------------
82 {
84 }
85
86 //------------------------------------------------------------------------------------------------
89 int GetBlastEffects(out notnull array<SCR_WeaponBlastEffect> outBlastEffects)
90 {
91 foreach (SCR_WeaponBlastEffect effect : m_aBlastEffects)
92 {
93 if (!effect)
94 continue;
95
96 outBlastEffects.Insert(effect);
97 }
98
99 return outBlastEffects.Count();
100 }
101}
102
103class SCR_WeaponBlastComponent : ScriptComponent
104{
105 [Attribute(desc: "Position and direction in which blast will propagate\nDirection is determined by forward vector (blue arrow)")]
106 protected ref PointInfo m_BlastOrigin;
108 protected ref TraceSphere m_Trace;
109 protected ref Instigator m_Instigator;
110 protected ref array<IEntity> m_aFoundCharacters;
111 protected IEntity m_VerifiedEntity;
112 protected bool m_bIsAiCharacter;
114 static protected const EQueryEntitiesFlags QUERY_FLAGS = EQueryEntitiesFlags.DYNAMIC;
115 static protected const int MAX_BLAST_MEMBERS = 32;
116#ifdef ENABLE_DIAG
117 // Debug
118 static protected ref SCR_DebugShapeManager m_DebugShapeMgr;
119 protected const float DEBUG_SPHERE_RADIUS = 0.1;
120 protected const ShapeFlags DEBUG_SHAPE_FLAGS = ShapeFlags.WIREFRAME | ShapeFlags.NOZBUFFER | ShapeFlags.TRANSP;
121 protected const int DEBUG_CONE_SUBDIV = 8;
122 protected const int dbgPanelXSize = 2;
123 protected const int dbgPanelYSize = 20;
124 protected ref array<ref TStringArray> m_aDebugData;
126 //------------------------------------------------------------------------------------------------
130 protected void AddDebugInfo(string label, string entry)
132 foreach (TStringArray arr : m_aDebugData)
133 {
134 if (arr[0] != label)
135 continue;
136
137 arr.Insert(entry);
138 return;
139 }
140
141 m_aDebugData.Insert({label, entry});
142 }
144 //------------------------------------------------------------------------------------------------
147 protected string GetPrefabName(IEntity ent)
148 {
149 string prefab = SCR_ResourceNameUtils.GetPrefabName(ent);
150 return FilePath.StripPath(prefab);
151 }
152
153 //------------------------------------------------------------------------------------------------
155 //! \param[in] entryName which is unique to other entries in this window
156 //! \param[in] xSize of the color indicator
157 //! \param[in] ySize of the color indicator
158 //! \param[in] color of the indicator
159 //! \param[in] text
160 protected void AddDebugLegendEntry(string entryName, int xSize, int ySize, int color, string text)
162 DbgUI.Panel(entryName, xSize, ySize, color);
163 DbgUI.SameLine();
164 DbgUI.Text(text);
165 }
166#endif
168 //------------------------------------------------------------------------------------------------
170 protected void CalculateRicochetDirection(vector hitPosDirNorm[3], out vector ricochetTransform[4], float ricochetFlatteningStrength = 1)
171 {
172 if (!ricochetTransform)
173 ricochetTransform = {};
174
175 vector dirInverted = -hitPosDirNorm[1];
176
177 // Matrix of surface normal
178 vector normalMatrix[3];
179 Math3D.MatrixFromUpVec(hitPosDirNorm[2], normalMatrix);
180
181 // Put direction vector into normal space
182 vector dirInvertedNormalSpace = dirInverted.InvMultiply3(normalMatrix);
183
184 // Create rotation matrix to deflect incoming vector
185 vector rotationMatrix[3];
186 Math3D.AnglesToMatrix({180, 0, 0}, rotationMatrix);
187
188 // Rotate normal matrix
189 vector rotatedNormalMatrix[3];
190 Math3D.MatrixMultiply3(normalMatrix, rotationMatrix, rotatedNormalMatrix);
191
192 // Return direction vector back to world space using rotated normal matrix
193 vector outDirectionDeflected = dirInvertedNormalSpace.Multiply3(rotatedNormalMatrix);
194
195 // Create vector perpendicular to surface normal and combine with deflected vector scaled by m_fDeflectionFlatteningStrength
196 vector outDirectionPerpendicular = (dirInverted * hitPosDirNorm[2]) * hitPosDirNorm[2];
197 vector outDirection = (outDirectionDeflected + outDirectionPerpendicular * ricochetFlatteningStrength).Normalized();
198 Math3D.DirectionAndUpMatrix(outDirection, outDirection.Perpend(), ricochetTransform);
199 ricochetTransform[3] = hitPosDirNorm[0] + outDirection * 0.1;//move away from that surface to not block the trace or start the trace inside of it
200 }
201
202 //------------------------------------------------------------------------------------------------
204 protected bool QueryFilter(IEntity ent)
205 {
206 if (ent == GetOwner())
207 return false;
208
209 if (!ChimeraCharacter.Cast(ent))
210 return false;
211
212 if (ent == m_VerifiedEntity)
213 return false;
214
215 return true;
216 }
217
218 //------------------------------------------------------------------------------------------------
220 protected bool QueryAddEntity(IEntity ent)
221 {
222 if (!ent)
223 return true;
224
225 if (m_aFoundCharacters.Contains(ent))
226 return true;
227
228 m_aFoundCharacters.Insert(ent);
229 return true;
230 }
231
232 //------------------------------------------------------------------------------------------------
233 // Method used to filter out objects that shouldnt block the blast like particles or worn equipment
234 protected bool TraceFilter(notnull IEntity ent)
235 {
236 if (!ent.GetPrefabData())
237 return false;
238
239 if (ent == ent.GetRootParent())
240 return true;
241
242 IEntity parent = ent.GetParent();
243 while (parent)
244 {
245 if (parent == GetOwner())
246 return false;
247
248 parent = parent.GetParent();
249 }
250
251 return true;
252 }
253
254 //------------------------------------------------------------------------------------------------
256 protected bool ObstructionTraceFilter(notnull IEntity e, vector start = "0 0 0", vector dir = "0 0 0")
257 {
258 if (m_VerifiedEntity == e)
259 return true; // our target should be included
260
261 IEntity parent = e.GetParent();
262 while (parent)
263 {//ignore character items
264 if (ChimeraCharacter.Cast(parent))
265 return false;
266
267 parent = parent.GetParent();
268 }
269
270 return true;
271 }
272
273 //------------------------------------------------------------------------------------------------
275 void OverrideInstigator(IEntity newInstigatorEntity)
276 {
277 m_Instigator = Instigator.CreateInstigator(newInstigatorEntity);
278 }
279
280 //------------------------------------------------------------------------------------------------
282 protected vector FindClosestCharacterPoint(vector startingPos, vector direction, notnull ChimeraCharacter character, out float distance)
283 {
284 const vector charAimPosition = character.AimingPosition();//center of mass of the character
285 float distanceAim = vector.Distance(startingPos, charAimPosition);
286 vector charDirection = vector.Direction(startingPos, charAimPosition).Normalized();
287 const float dotAim = vector.Dot(charDirection, direction);
288 const vector nearestPositionAim = startingPos + direction * dotAim * distanceAim;
289 distanceAim = vector.Distance(nearestPositionAim, charAimPosition);
290
291 //Compare this with EYE position
292 vector charPosition = character.EyePosition();
293 distance = vector.Distance(startingPos, charPosition);
294 charDirection = vector.Direction(startingPos, charPosition).Normalized();
295 float dot = vector.Dot(charDirection, direction);
296 vector nearestPosition = startingPos + direction * dot * distance;
297 if (distanceAim > vector.Distance(nearestPosition, charPosition) && dot >= 0 && dot > dotAim)
298 {
299#ifdef ENABLE_DIAG
300 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) == 3)
301 {//GREEN SPHERE == Character eye position being the closest, usable position
302 m_DebugShapeMgr.AddSphere(charPosition, DEBUG_SPHERE_RADIUS * 0.3, Color.SPRING_GREEN, DEBUG_SHAPE_FLAGS);
303 m_DebugShapeMgr.AddLine(charPosition, nearestPosition, Color.SPRING_GREEN);
304 m_DebugShapeMgr.AddSphere(nearestPosition, DEBUG_SPHERE_RADIUS * 0.3, Color.DARK_GREEN, DEBUG_SHAPE_FLAGS);
305
306 AddDebugInfo("Distance:", "Eyes - " + GetPrefabName(character) + " at " + charPosition + " distance = " + distance.ToString(lenDec: 2));
307 }
308#endif
309 return charPosition;
310 }
311
312 //If eye position wasnt closer then check root position
313 charPosition = character.GetOrigin();
314 distance = vector.Distance(startingPos, charPosition);
315 charDirection = vector.Direction(startingPos, charPosition).Normalized();
316 dot = vector.Dot(charDirection, direction);
317 nearestPosition = startingPos + direction * dot * distance;
318 if (distanceAim > vector.Distance(nearestPosition, charPosition) && dot >= 0 && dot > dotAim)
319 {
320#ifdef ENABLE_DIAG
321 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) == 3)
322 {//GRAY SPHERE == Character root position being the closest, usable position
323 m_DebugShapeMgr.AddSphere(charPosition, DEBUG_SPHERE_RADIUS * 0.3, Color.GRAY_25, DEBUG_SHAPE_FLAGS);
324 m_DebugShapeMgr.AddLine(charPosition, nearestPosition, Color.SPRING_GREEN);
325 m_DebugShapeMgr.AddSphere(nearestPosition, DEBUG_SPHERE_RADIUS * 0.3, Color.GRAY, DEBUG_SHAPE_FLAGS);
326
327 AddDebugInfo("Distance:", "Root - " + GetPrefabName(character) + " at " + charPosition + " distance = " + distance.ToString(lenDec: 2));
328 }
329#endif
330 return charPosition;
331 }
332
333#ifdef ENABLE_DIAG
334 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) == 3)
335 {//BLUE SPHERE == Character center of mass being the closest, usable position
336 m_DebugShapeMgr.AddSphere(charAimPosition, DEBUG_SPHERE_RADIUS * 0.3, Color.DODGER_BLUE, DEBUG_SHAPE_FLAGS);
337 m_DebugShapeMgr.AddLine(charAimPosition, nearestPositionAim, Color.SPRING_GREEN);
338 m_DebugShapeMgr.AddSphere(nearestPositionAim, DEBUG_SPHERE_RADIUS * 0.3, Color.DARK_BLUE, DEBUG_SHAPE_FLAGS);
339
340 AddDebugInfo("Distance:", "Center of mass - " + GetPrefabName(character) + " at " + charAimPosition + " distance = " + distanceAim.ToString(lenDec: 2));
341 }
342#endif
343 distance = distanceAim;
344 return charAimPosition;
345 }
346
347 //------------------------------------------------------------------------------------------------
348 protected bool GetClosestHitZonePosition(out vector entPosition, notnull ChimeraCharacter character, vector nearestPosition)
349 {
350 SCR_CharacterDamageManagerComponent damageMgr = SCR_CharacterDamageManagerComponent.Cast(character.GetDamageManager());
351 if (!damageMgr)
352 return false;
353
354 Physics physics = character.GetPhysics();
355 if (!physics)
356 return false;
357
358 array<HitZone> charHitZones = {};
359 damageMgr.GetAllHitZones(charHitZones);
360 if (charHitZones.IsEmpty())
361 return false;
362
363 float colliderDistance;
364 vector chosenCollider;
365 vector colliderTransform[4] = {};
366 array<int> colliderIDs = {};
367 foreach (HitZone hitZone : charHitZones)
368 {
369 if (!hitZone.HasColliderNodes())
370 continue;
371
372 colliderIDs.Clear();
373 hitZone.GetColliderIDs(colliderIDs);
374 foreach (int ID : colliderIDs)
375 {
376 physics.GetGeomWorldTransform(ID, colliderTransform);
377 colliderDistance = vector.DistanceSq(nearestPosition, colliderTransform[3]);
378 if (chosenCollider[2] == 0 || chosenCollider[0] > colliderDistance)
379 {
380 chosenCollider = {colliderDistance, ID, 1};
381 entPosition = colliderTransform[3];
382 }
383 }
384 }
385
386 return chosenCollider[2] != 0;
387 }
388
389 //------------------------------------------------------------------------------------------------
390 protected void QueryBlastedCharacters(vector startingPos[4], float length, out notnull array<ref SCR_BlastedEntityEntry> blastedEntities, float additionalDistance = 0)
391 {
392 if (!m_aFoundCharacters)
393 m_aFoundCharacters = {};
394 else
395 m_aFoundCharacters.Clear();
396
398 if (!data)
399 return;
400
401 const float blastLength = data.GetBlastLength();
402 const float coneAngle = data.GetBlastConeAngle();
403 const BaseWorld world = GetOwner().GetWorld();
404 const float radius = Math.Clamp(length * Math.Tan(coneAngle * Math.DEG2RAD), 0, length);
405 const vector mins = {-radius, -radius, 0};
406 const vector maxs = {radius, radius, length};
407
408 //Query characters that are in the danger zone
409 world.QueryEntitiesByOBB(mins, maxs, startingPos, QueryAddEntity, QueryFilter, QUERY_FLAGS);
410
411 const vector direction = vector.Direction(startingPos[3], vector.Forward.Multiply3(startingPos) * length + startingPos[3]).Normalized();
412
413#ifdef ENABLE_DIAG
414 int debugValue = DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST);
415 bool showDebug = debugValue < 5;
416 if (additionalDistance != 0)
417 showDebug = debugValue < 4 || debugValue == 5;
418
419 if (showDebug && debugValue != 0)
420 {
421 if (debugValue == 2)//BLACK == Area of query
422 m_DebugShapeMgr.AddRectangle(startingPos[3], direction, length, radius * 2, Color.BLACK);
423
424 //WHITE == AoE of the main blast
425 if (additionalDistance == 0)
426 m_DebugShapeMgr.Add(CreateCone(startingPos[3], direction, coneAngle, coneAngle, length / Math.Cos(coneAngle * Math.DEG2RAD), Color.WHITE, DEBUG_CONE_SUBDIV, ShapeFlags.NOZBUFFER | ShapeFlags.TRANSP));
427 else //YELLOW == AoE of the ricochet
428 m_DebugShapeMgr.AddTrapezoidalPrism(startingPos[3], direction, additionalDistance * Math.Tan(coneAngle * Math.DEG2RAD), (length + additionalDistance) * Math.Tan(coneAngle * Math.DEG2RAD), length, DEBUG_CONE_SUBDIV, Color.YELLOW, ShapeFlags.NOZBUFFER | ShapeFlags.TRANSP);
429 }
430#endif
431
432 if (m_aFoundCharacters.IsEmpty())
433 return;
434
435 if (m_aFoundCharacters.Count() > MAX_BLAST_MEMBERS)
436 {//Sorting is expensive so do it only when we need it
437 SCR_EntityHelper.QuickSortEntitiesByDistanceToPoint(m_aFoundCharacters, startingPos[3], 0, m_aFoundCharacters.Count() - 1);
438 m_aFoundCharacters.Resize(MAX_BLAST_MEMBERS);
439 }
440
441 const float maxCos = Math.Cos(coneAngle * Math.DEG2RAD);
442 float distance, dot;
443 vector hitPosDirNorm[3];
444 vector entDirection, entPosition, nearestPosition;
445 CharacterControllerComponent controller;
446
447 //Prepare reusable part of the trace
448 TraceParam traceParam = new TraceParam();
449 traceParam.Start = startingPos[3];
450 traceParam.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
451 traceParam.LayerMask = EPhysicsLayerDefs.Projectile;
452 traceParam.Exclude = GetOwner();
453
454 //Filter found entities
455 ChimeraCharacter character;
456 foreach (IEntity ent : m_aFoundCharacters)
457 {
458 character = ChimeraCharacter.Cast(ent);
459 if (!character)
460 continue;//Shouldnt happen but just in case reject those
461
462 if (m_bIsAiCharacter && !data.CanAIDamageItself() && character == m_Instigator.GetInstigatorEntity())
463 continue;//Skip if ai shouldnt harm itself
464
465 controller = character.GetCharacterController();
466 if (!controller || controller.GetLifeState() == ECharacterLifeState.DEAD)
467 continue;//Ignore the dead to not waste time
468
469 if (!controller.IsPlayerControlled() && data.ShouldIgnoreAIUnits())
470 continue;
471
472 nearestPosition = FindClosestCharacterPoint(startingPos[3], direction, character, distance);
473 if (distance > length)
474 {//Reject those that seem too far away
475#ifdef ENABLE_DIAG
476 if (showDebug && (debugValue == 2 || debugValue >= 4))
477 {//VIOLET || PINK == too far from the source
478 if (character == m_Instigator.GetInstigatorEntity())
479 m_DebugShapeMgr.AddArrow(nearestPosition + vector.Up, nearestPosition, 0, Color.PINK);
480 else
481 m_DebugShapeMgr.AddArrow(nearestPosition + vector.Up, nearestPosition, 0, Color.VIOLET);
482
483 AddDebugInfo("Distance:", "Rejected " + GetPrefabName(character) + " at " + nearestPosition + " - too far");
484 }
485#endif
486 continue;
487 }
488
489 entDirection = vector.Direction(startingPos[3], nearestPosition).Normalized();
490 dot = vector.Dot(direction, entDirection);
491 if (dot < 0)
492 {//Reject those that seem to be in the wrong direction
493#ifdef ENABLE_DIAG
494 if (showDebug && (debugValue == 2 || debugValue >= 4))
495 {//CYAN == wrong direction
496 if (character == m_Instigator.GetInstigatorEntity())
497 m_DebugShapeMgr.AddArrow(nearestPosition + vector.Up, nearestPosition, 0, Color.CYAN);
498 else
499 m_DebugShapeMgr.AddArrow(nearestPosition + vector.Up, nearestPosition, 0, Color.DARK_CYAN);
500
501 AddDebugInfo("Direction:", "Rejected " + GetPrefabName(character) + " at " + nearestPosition + " - wrong direction");
502 }
503#endif
504 continue;
505 }
506
507 //Find nearest hit zone position
508 if (!GetClosestHitZonePosition(entPosition, character, nearestPosition))
509 continue;//Reject if we didnt find any usable position
510
511 //Check the range agian this time to the actuall hitzone to ensure that its in range
512 distance = vector.Distance(entPosition, startingPos[3]);
513 if (distance > length)
514 {
515#ifdef ENABLE_DIAG
516 if (showDebug && (debugValue == 2 || debugValue >= 4))
517 {//GREEN == too far from the source
518 if (character == m_Instigator.GetInstigatorEntity())
519 m_DebugShapeMgr.AddArrow(entPosition + vector.Up, entPosition, 0, Color.SPRING_GREEN);
520 else
521 m_DebugShapeMgr.AddArrow(entPosition + vector.Up, entPosition, 0, Color.DARK_GREEN);
522
523 AddDebugInfo("Distance:", "Rejected " + GetPrefabName(character) + " at " + entPosition + " - too far");
524 }
525#endif
526 continue;
527 }
528
529 entDirection = vector.Direction(startingPos[3], entPosition).Normalized();
530 dot = vector.Dot(direction, entDirection);
531 //In case of ricochet just check if its within 90deg arc and then recalculate dot compensating for the starting size of the ricochet blast area
532 if (additionalDistance > 0 && dot >= 0 && dot < maxCos)
533 dot = vector.Dot(vector.Direction(startingPos[3] - vector.Forward.Multiply3(startingPos) * additionalDistance, entPosition).Normalized(), direction);
534
535 //Reject based on the angle from the blast origin
536 if (dot < maxCos)
537 {
538#ifdef ENABLE_DIAG
539 if (showDebug && (debugValue == 2 || debugValue >= 4))
540 {//BLUE == too much deviation from forward vector
541 if (character == m_Instigator.GetInstigatorEntity())
542 m_DebugShapeMgr.AddArrow(entPosition + vector.Up, entPosition, 0, Color.DODGER_BLUE);
543 else
544 m_DebugShapeMgr.AddArrow(entPosition + vector.Up, entPosition, 0, Color.DARK_BLUE);
545
546 AddDebugInfo("Angle:", "Rejected " + GetPrefabName(character) + " at " + entPosition + " - outside of the max angle");
547 }
548#endif
549 continue;
550 }
551
552 traceParam.End = entPosition;
553 traceParam.TraceEnt = null;
554 m_VerifiedEntity = character;
555 world.TraceMove(traceParam, ObstructionTraceFilter);
556
557 //Reject if there is something else on the way to the target
558 if (traceParam.TraceEnt && character != traceParam.TraceEnt)
559 {
560#ifdef ENABLE_DIAG
561 if (showDebug && (debugValue == 2 || debugValue >= 4))
562 {//GRAY == something on the way to target
563 if (character == m_Instigator.GetInstigatorEntity())
564 m_DebugShapeMgr.AddArrow(entPosition + vector.Up, entPosition, 0, Color.GRAY_25);
565 else
566 m_DebugShapeMgr.AddArrow(entPosition + vector.Up, entPosition, 0, Color.GRAY);
567
568 AddDebugInfo("Obstructed:", "At " + entPosition + " " + GetPrefabName(character) + " by " + GetPrefabName(traceParam.TraceEnt));
569 }
570#endif
571 continue;
572 }
573
574 hitPosDirNorm[0] = entDirection * distance + traceParam.Start;
575 hitPosDirNorm[1] = entDirection;
576 hitPosDirNorm[2] = traceParam.TraceNorm.Normalized();
577
578 SCR_BlastedEntityEntry entry = new SCR_BlastedEntityEntry(character, hitPosDirNorm[0], hitPosDirNorm[1], hitPosDirNorm[2], dot, vector.Distance(startingPos[3], entPosition) + additionalDistance, traceParam.NodeIndex, traceParam.ColliderIndex, traceParam.SurfaceProps);
579 blastedEntities.Insert(entry);
580#ifdef ENABLE_DIAG
581 if (showDebug && debugValue != 0)
582 {//RED == hit
583 if (character == m_Instigator.GetInstigatorEntity())
584 m_DebugShapeMgr.AddArrow(traceParam.Start, entPosition, 0, Color.RED);
585 else
586 m_DebugShapeMgr.AddArrow(traceParam.Start, entPosition, 0, Color.DARK_RED);
587
588 AddDebugInfo("Hit:", GetPrefabName(character) + " at " + entPosition);
589 }
590#endif
591 }
592
593 m_aFoundCharacters.Clear();
594 m_VerifiedEntity = null;
595 }
596
597 //------------------------------------------------------------------------------------------------
599 protected void OnWeaponFired(IEntity effectEntity, BaseMuzzleComponent muzzle, IEntity projectileEntity)
600 {
601 if (!m_Instigator || m_Instigator.GetInstigatorType() == InstigatorType.INSTIGATOR_NONE)
602 {
603 SCR_ChimeraCharacter ownerCharacter = GetCharacterOwner();
604 m_Instigator = Instigator.CreateInstigator(ownerCharacter);
605 }
606
607 m_bIsAiCharacter = !SCR_CharacterHelper.IsAPlayer(m_Instigator.GetInstigatorEntity());
608 vector startTransform[4];
609 m_BlastOrigin.GetWorldTransform(startTransform);
610 array<ref SCR_BlastedEntityEntry> blastedEntities = {};
611#ifdef ENABLE_DIAG
612 if (!m_DebugShapeMgr)
613 m_DebugShapeMgr = new SCR_DebugShapeManager();
614 else
615 m_DebugShapeMgr.Clear();
616
617 DbgUI.BeginCleanupScope();
618 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) != 0)
619 {
620 DbgUI.Begin("Weapon blast legend");
621 AddDebugLegendEntry("VIOLET", dbgPanelXSize, dbgPanelYSize, Color.VIOLET, "VIOLET Arrow - Rejected. Target entity is too far");
622 AddDebugLegendEntry("PINK", dbgPanelXSize, dbgPanelYSize, Color.PINK, "PINK Arrow - Rejected. Character who caused the blast is too far");
623 AddDebugLegendEntry("CYAN", dbgPanelXSize, dbgPanelYSize, Color.CYAN, "CYAN Arrow - Rejected. Outside of the area of influence");
624 AddDebugLegendEntry("GREEN", dbgPanelXSize, dbgPanelYSize, Color.GREEN, "GREEN Arrow - Rejected. Selected hit zone is too far from the blast source");
625 AddDebugLegendEntry("BLUE", dbgPanelXSize, dbgPanelYSize, Color.BLUE, "BLUE Arrow - Rejected. Direction to it deviates too much from the direction of the blast");
626 AddDebugLegendEntry("GRAY", dbgPanelXSize, dbgPanelYSize, Color.GRAY, "GRAY Arrow - Rejected. Something obstructing it");
627 AddDebugLegendEntry("RED", dbgPanelXSize, dbgPanelYSize, Color.RED, "RED Arrow - Object hit");
628 AddDebugLegendEntry("GREEN_S", dbgPanelYSize, dbgPanelYSize, Color.GREEN, "GREEN Sphere - Using character eye position as it is the closest point");
629 AddDebugLegendEntry("GRAY_S", dbgPanelYSize, dbgPanelYSize, Color.GRAY, "GRAY Sphere - Using character root position as it is the closest point");
630 AddDebugLegendEntry("BLUE_S", dbgPanelYSize, dbgPanelYSize, Color.BLUE, "BLUE Sphere - Using character center of mass as it is the closest point");
631 AddDebugLegendEntry("BLACK_S", dbgPanelYSize, dbgPanelYSize, Color.BLACK, "BLACK Sphere - Rejected. Inssuficient amount of damage");
632 AddDebugLegendEntry("RED_S", dbgPanelYSize, dbgPanelYSize, Color.RED, "RED Sphere - Object damaged");
633 DbgUI.End();
634
635 if (!m_aDebugData)
636 m_aDebugData = {};
637 else
638 m_aDebugData.Clear();
639 }
640#endif
641
642 IEntity owner = GetOwner();
644 if (!data)
645 return;
646
647 float blastLength = data.GetBlastLength();
648 if (!m_bIsAiCharacter || data.CanAIBlastCharacters())
649 QueryBlastedCharacters(startTransform, blastLength, blastedEntities);
650
651 if (data.CanBlastRicochet())
652 {
653 //Trace for a surface to ricochet from
654 BaseWorld world = owner.GetWorld();
655 TraceParam traceParam = new TraceParam();
656 traceParam.Start = startTransform[3];
657 traceParam.End = vector.Forward.Multiply3(startTransform) * blastLength + traceParam.Start;
658 traceParam.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
659 array<IEntity> excludeArray = {owner, m_Instigator.GetInstigatorEntity()};
660 traceParam.ExcludeArray = excludeArray;
661 traceParam.LayerMask = EPhysicsLayerDefs.Projectile;
662 float hit = world.TraceMove(traceParam, TraceFilter);
663
664 if (traceParam.TraceEnt && hit < 1 && !float.AlmostEqual(hit, 1, 0.1))
665 {
666 bool ricochetFromDamageEnt;
667 foreach (SCR_BlastedEntityEntry entry : blastedEntities)
668 {
669 if (entry.GetTargetEntity() == traceParam.TraceEnt)
670 {
671 ricochetFromDamageEnt = true;
672 entry.SetAngleToTarget(1);
673 break;
674 }
675 }
676
677 vector hitPosDirNorm[3];
678 vector dir = traceParam.End - traceParam.Start;
679 hitPosDirNorm[0] = dir * hit + traceParam.Start;
680 hitPosDirNorm[1] = dir.Normalized();
681 hitPosDirNorm[2] = traceParam.TraceNorm.Normalized();
682
683 float hitDistance = blastLength * hit;
684 bool ricochetFromCharacter = ChimeraCharacter.Cast(traceParam.TraceEnt) != null;
685 if (!ricochetFromDamageEnt && (!m_bIsAiCharacter || !ricochetFromCharacter && data.CanAIBlastDestructible() || ricochetFromCharacter && data.CanAIBlastCharacters()))
686 blastedEntities.Insert(new SCR_BlastedEntityEntry(traceParam.TraceEnt, hitPosDirNorm[0], hitPosDirNorm[1], hitPosDirNorm[2], 1, hitDistance, traceParam.NodeIndex, traceParam.ColliderIndex, traceParam.SurfaceProps));
687
688 CalculateRicochetDirection(hitPosDirNorm, startTransform, data.GetDeflectionFlatteningStrength());
689#ifdef ENABLE_DIAG
690 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) != 0)
691 {
692 m_DebugShapeMgr.AddArrow(traceParam.Start, hitPosDirNorm[0], 0, Color.ORANGE);
693 m_DebugShapeMgr.AddSphere(startTransform[3], DEBUG_SPHERE_RADIUS, Color.ORANGE, DEBUG_SHAPE_FLAGS);
694 }
695#endif
696 m_VerifiedEntity = traceParam.TraceEnt;//to ignore it in the query
697
698 if (!m_bIsAiCharacter || data.CanAIBlastCharacters())
699 QueryBlastedCharacters(startTransform, blastLength - hitDistance, blastedEntities, hitDistance);
700 }
701 }
702
703 if (blastedEntities.Count() > 0)
704 ApplyDamage(blastedEntities);
705
706 m_Instigator = Instigator.CreateInstigator(null);
707 m_bIsAiCharacter = false;
708#ifdef ENABLE_DIAG
709 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) != 0 && m_aDebugData && !m_aDebugData.IsEmpty())
710 {
711 // This is necessary as otherwise the list is not going to be recreated with next shot and thus it will have old content
712 const string time = System.GetUnixTime().ToString();
713 int outSelection;
714
715 DbgUI.Begin("Weapon blast details", y: 300);
716 foreach (int i, TStringArray arr : m_aDebugData)
717 {
718 DbgUI.List("List_" + i + time, outSelection, arr);
719 }
720
721 DbgUI.End();
722 }
723 DbgUI.EndCleanupScope();
724#endif
725 }
726
727 //------------------------------------------------------------------------------------------------
728 protected void ApplyDamage(notnull array<ref SCR_BlastedEntityEntry> blastedEntities)
729 {
730 IEntity owner = GetOwner();
732 if (!data)
733 return;
734
735 array<SCR_WeaponBlastEffect> blastEffects = {};
736 if (data.GetBlastEffects(blastEffects) < 1)
737 return;
738
739 bool isCharacter;
740 SCR_DestructibleEntity destructibleEntity;
741 vector hitPosDirNorm[3];
742 SCR_DamageContext context;
743 SCR_DamageManagerComponent damageManager;
744 HitZone hitZone;
745 float maxHealth;
746 float computedDamageAmount;
747 float damageValue;
748 const float blastLength = data.GetBlastLength();
749 const float destructibleDamageMultiplier = data.GetDestructibleDamageMultiplier();
750
751 foreach (SCR_BlastedEntityEntry entry : blastedEntities)
752 {
753 //Check if the entity is destructible entity
754 destructibleEntity = SCR_DestructibleEntity.Cast(entry.GetTargetEntity());
755 if (destructibleEntity)
756 {
757 foreach (int i, SCR_WeaponBlastEffect effect : blastEffects)
758 {
759 damageValue = effect.GetComputedDamage(entry.GetDistanceToTarget() / blastLength, entry.GetAngleToTarget()) * destructibleDamageMultiplier;
760
761 entry.GetTargetHitPosDirNorm(hitPosDirNorm);
762 destructibleEntity.HandleDamage(effect.GetDamageType(), damageValue, hitPosDirNorm);
763#ifdef ENABLE_DIAG
764 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) != 0)
765 {//TEAL SPHERE == Damage amount for destructible entity
766 float factor = Math.Lerp(0, 1, (1 - entry.GetDistanceToTarget() / blastLength) * entry.GetAngleToTarget());
767 Color damageColor = new Color(factor * 0.1, factor, factor, 1);
768 m_DebugShapeMgr.AddSphere(hitPosDirNorm[0], DEBUG_SPHERE_RADIUS + i * 0.05, damageColor.PackToInt(), DEBUG_SHAPE_FLAGS);
769
770 AddDebugInfo("Damage dealt:", GetPrefabName(destructibleEntity) + " at " + hitPosDirNorm[0] + ", value = " + damageValue.ToString(lenDec: 3));
771 }
772#endif
773 }
774 continue;
775 }
776
777 isCharacter = ChimeraCharacter.Cast(entry.GetTargetEntity()) != null;
778 //Check if the entity has the damage manager component
779 damageManager = SearchHierarchyForDamageManager(entry.GetTargetEntity(), entry.GetTargetColliderId(), hitZone);
780 if (!damageManager || !hitZone)
781 continue;
782
783 entry.GetTargetHitPosDirNorm(hitPosDirNorm);
784 maxHealth = hitZone.GetMaxHealth();
785 foreach (int i, SCR_WeaponBlastEffect effect : blastEffects)
786 {
787 damageValue = effect.GetComputedDamage(entry.GetDistanceToTarget() / blastLength, entry.GetAngleToTarget());
788 if (!isCharacter)
789 damageValue *= destructibleDamageMultiplier;//Damage multiplier for destructibles
790
791 context = new SCR_DamageContext(effect.GetDamageType(), damageValue, hitPosDirNorm,
792 entry.GetTargetEntity(), hitZone, m_Instigator,
793 entry.GetTargetSurfaceProps(), entry.GetTargetColliderId(), entry.GetTargetNodeId());
794
795 context.damageEffect = effect.GetDamageEffect();
796 context.damageSource = owner;
797 computedDamageAmount = hitZone.ComputeEffectiveDamage(context, false);
798 if (computedDamageAmount == 0 || computedDamageAmount / maxHealth < 0.01)
799 {
800#ifdef ENABLE_DIAG
801 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) != 0)
802 {//BLACK SPHERE == NOT ENOUGH DAMAGE
803 m_DebugShapeMgr.AddSphere(hitPosDirNorm[0], DEBUG_SPHERE_RADIUS + i * 0.05, Color.BLACK, DEBUG_SHAPE_FLAGS);
804
805 AddDebugInfo("Damage insufficient:", "Effect nr = " + i + " " + GetPrefabName(context.hitEntity) + " at " + context.hitPosition + ", value = " + computedDamageAmount + " < " + (maxHealth * 0.01));
806 }
807#endif
808 continue;//Skip damage handling if damage is negligible
809 }
810
811 damageManager.HandleDamage(context);
812#ifdef ENABLE_DIAG
813 if (DiagMenu.GetValue(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST) != 0)
814 {//RED SPHERE == DAMAGE AMOUNT
815 float factor = Math.Lerp(0, 1, context.damageValue / effect.GetDamageValueRaw());
816 Print(factor);
817 Color damageColor = new Color(factor, factor * 0.1, factor * 0.1, 1);
818 m_DebugShapeMgr.AddSphere(hitPosDirNorm[0], DEBUG_SPHERE_RADIUS + i * 0.05, damageColor.PackToInt(), DEBUG_SHAPE_FLAGS);
819
820 AddDebugInfo("Damage dealt:", "Effect nr = " + i + " " + GetPrefabName(context.hitEntity) + " at " + context.hitPosition + ", value = " + context.damageValue.ToString(lenDec: 3) + " (max dmg = " + effect.GetDamageValueRaw() + ")");
821 }
822#endif
823 }
824 }
825 }
826
827 //------------------------------------------------------------------------------------------------
828 protected SCR_DamageManagerComponent SearchHierarchyForDamageManager(IEntity startEntity, int colliderIndex, out HitZone hitZone)
829 {
830 if (!startEntity)
831 return null;
832
833 SCR_DamageManagerComponent damageManager;
834 while (startEntity)
835 {
836 damageManager = SCR_DamageManagerComponent.GetDamageManager(startEntity);
837 if (damageManager)
838 break;
839
840 startEntity = startEntity.GetParent();
841 }
842
843 if (damageManager)
844 {
845 hitZone = damageManager.GetHitZoneByColliderID(colliderIndex);
846 if (!hitZone)
847 hitZone = damageManager.GetDefaultHitZone();
848 }
849
850 return damageManager;
851 }
852
853 //------------------------------------------------------------------------------------------------
855 protected SCR_ChimeraCharacter GetCharacterOwner()
856 {
857 IEntity owner = GetOwner();
858 if (!owner)
859 return null;
860
861 if (Turret.Cast(owner))
862 {
863 SCR_BaseCompartmentManagerComponent compartmentManager = SCR_BaseCompartmentManagerComponent.Cast(owner.FindComponent(SCR_BaseCompartmentManagerComponent));
864 if (!compartmentManager || !compartmentManager.AnyCompartmentsOccupiedOrLocked())
865 return null;
866
867 array<BaseCompartmentSlot> outCompartments = {};
868 if (compartmentManager.GetCompartments(outCompartments) < 1)
869 return null;
870
871 foreach (BaseCompartmentSlot compartment : outCompartments)
872 {
873 if (TurretCompartmentSlot.Cast(compartment))
874 return SCR_ChimeraCharacter.Cast(compartment.GetOccupant());
875 }
876 }
877 else
878 {
879 IEntity parent = owner.GetParent();
880 SCR_ChimeraCharacter character;
881 while (parent)
882 {
883 character = SCR_ChimeraCharacter.Cast(parent);
884 if (character)
885 return character;
886
887 parent = parent.GetParent();
888 }
889 }
890
891 return null;
892 }
893
894 //------------------------------------------------------------------------------------------------
895 override void OnDelete(IEntity owner)
896 {
898 if (!muzzleEffectComponent)
899 return;
900
901 muzzleEffectComponent.GetOnWeaponFired().Remove(OnWeaponFired);
902 }
903
904 //------------------------------------------------------------------------------------------------
905 override void EOnInit(IEntity owner)
906 {
907 RplComponent rplComp = SCR_EntityHelper.GetEntityRplComponent(owner);
908 if (rplComp && rplComp.Role() != RplRole.Authority)
909 return;
910
912 if (!data)
913 return;
914
915 if (!m_BlastOrigin)
916 return;
917
918 if (data.GetBlastLength() == 0)
919 return;
920
922 if (!muzzleEffectComponent)
923 return;
924
925 muzzleEffectComponent.GetOnWeaponFired().Insert(OnWeaponFired);
926 m_BlastOrigin.Init(owner);
927#ifdef ENABLE_DIAG
928 //NOTE: This is only present for the authority (f.e. selfhosted game or workbench)
929 DiagMenu.RegisterItem(SCR_DebugMenuID.DEBUGUI_WEAPONS_BLAST, "", "Visualize weapon blast", "Damage", "disabled,hit,all,posDebug,onlyMainBlast,onlyRicochet");
930#endif
931 }
932
933 //------------------------------------------------------------------------------------------------
934 override void OnPostInit(IEntity owner)
935 {
936 SetEventMask(owner, EntityEvent.INIT);
937 }
938}
SCR_DebugMenuID
This enum contains all IDs for DiagMenu entries added in script.
Definition DebugMenuID.c:4
Shape CreateCone(vector pos, vector aroundDir, float coneAngX, float coneAngY, float coneLength, int color, int subdivisions, ShapeFlags flags)
Definition DebugShapes.c:38
SCR_CharacterSoundComponentClass GetComponentData()
void SCR_DestructibleEntity(IEntitySource src, IEntity parent)
float distance
vector direction
Get all prefabs that have the spawner data
func OnWeaponFired
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
bool TraceFilter(notnull IEntity e)
void ApplyDamage(notnull array< ref SCR_BlastedEntityEntry > blastedEntities)
bool ObstructionTraceFilter(notnull IEntity e, vector start="0 0 0", vector dir="0 0 0")
Method used to determine which items that were found by the query should stop the trace.
vector FindClosestCharacterPoint(vector startingPos, vector direction, notnull ChimeraCharacter character, out float distance)
Method for finding the the point that is the closest to the center line of the cone.
bool QueryFilter(IEntity ent)
Method used for filtering out entities that are not meant to be gathered for further investigation of...
SCR_DamageManagerComponent SearchHierarchyForDamageManager(IEntity startEntity, int colliderIndex, out HitZone hitZone)
bool GetClosestHitZonePosition(out vector entPosition, notnull ChimeraCharacter character, vector nearestPosition)
void OverrideInstigator(IEntity newInstigatorEntity)
Method that is meant to be set before weapon will fire in order to control who is blamed for the blas...
SCR_ChimeraCharacter GetCharacterOwner()
Returns the top most owner of this object or the operator of this turret.
void QueryBlastedCharacters(vector startingPos[4], float length, out notnull array< ref SCR_BlastedEntityEntry > blastedEntities, float additionalDistance=0)
bool QueryAddEntity(IEntity ent)
Method used for adding found characters to the list of blsted characters.
Definition Color.c:13
Definition DbgUI.c:66
Diagnostic and developer menu system.
Definition DiagMenu.c:18
proto external int SetEventMask(notnull IEntity owner, int mask)
event float ComputeEffectiveDamage(notnull BaseDamageContext damageContext, bool isDOT)
proto external Managed FindComponent(typename typeName)
proto external BaseWorld GetWorld()
proto external IEntity GetParent()
Definition Math.c:13
static void QuickSortEntitiesByDistanceToPoint(notnull inout array< IEntity > arr, vector pos, int low, int high)
static RplComponent GetEntityRplComponent(notnull IEntity entity)
OnItemWeaponFiredInvoker GetOnWeaponFired()
int GetBlastEffects(out notnull array< SCR_WeaponBlastEffect > outBlastEffects)
ref array< ref SCR_WeaponBlastEffect > m_aBlastEffects
proto external GenericEntity GetOwner()
Get owner entity.
void EOnInit(IEntity owner)
ECharacterLifeState
InstigatorType
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
ShapeFlags
Definition ShapeFlags.c:13
SCR_FieldOfViewSettings Attribute
EntityEvent
Various entity events.
Definition EntityEvent.c:14
RplRole
Role of replicated node (and all items in it) within the replication system.
Definition RplRole.c:14
array< string > TStringArray
Definition Types.c:385
EQueryEntitiesFlags
TraceFlags
Definition TraceFlags.c:13
@ ID
Ordered by Group application ID.