Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/Explosion.cs
5 using FarseerPhysics;
6 using Microsoft.Xna.Framework;
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 
11 namespace Barotrauma
12 {
21  partial class Explosion
22  {
23  public readonly Attack Attack;
24 
28  private readonly float force;
29 
38  public float CameraShake { get; set; }
39 
48  public float CameraShakeRange { get; set; }
49 
53  private readonly Color screenColor;
54 
63  private readonly float screenColorRange;
64 
68  private readonly float screenColorDuration;
69 
73  private bool sparks;
74 
78  private bool shockwave;
79 
83  private bool flames;
84 
88  private bool smoke;
89 
93  private bool flash;
94 
98  private bool debris;
99 
103  private bool underwaterBubble;
104 
108  private readonly Color flashColor;
109 
113  private readonly bool playTinnitus;
114 
123  private readonly bool applyFireEffects;
124 
128  private readonly Identifier[] ignoreFireEffectsForTags;
129 
133  public bool IgnoreCover { get; set; }
134 
138  public bool DistanceFalloff { get; set; } = true;
139 
143  public IEnumerable<Structure> IgnoredCover;
144 
148  private readonly float flashDuration;
149 
153  private readonly float? flashRange;
154 
159  private readonly string decal;
160 
164  private readonly float decalSize;
165 
169  public bool OnlyInside;
170 
174  public bool OnlyOutside;
175 
179  public bool PlayDamageSounds;
180 
184  private readonly float itemRepairStrength;
185 
186  public readonly HashSet<Submarine> IgnoredSubmarines = new HashSet<Submarine>();
187 
188  public readonly HashSet<Character> IgnoredCharacters = new HashSet<Character>();
189 
193  public float EmpStrength { get; set; }
194 
198  public float BallastFloraDamage { get; set; }
199 
200  public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
201  {
202  Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, Math.Min(range, 1000000))
203  {
204  SeverLimbsProbability = 1.0f
205  };
206  this.force = force;
207  this.EmpStrength = empStrength;
208  BallastFloraDamage = ballastFloraStrength;
209  sparks = true;
210  debris = true;
211  shockwave = true;
212  smoke = true;
213  flames = true;
214  underwaterBubble = true;
215  ignoreFireEffectsForTags = Array.Empty<Identifier>();
216  }
217 
218  public Explosion(ContentXElement element, string parentDebugName)
219  {
220  Attack = new Attack(element, parentDebugName + ", Explosion");
221 
222  force = element.GetAttributeFloat("force", 0.0f);
223 
224  //the "abilityexplosion" field is kept for backwards compatibility (basically the opposite of "showeffects")
225  bool showEffects = !element.GetAttributeBool("abilityexplosion", false) && element.GetAttributeBool("showeffects", true);
226  sparks = element.GetAttributeBool("sparks", showEffects);
227  shockwave = element.GetAttributeBool("shockwave", showEffects);
228  flames = element.GetAttributeBool("flames", showEffects);
229  underwaterBubble = element.GetAttributeBool("underwaterbubble", showEffects);
230  smoke = element.GetAttributeBool("smoke", showEffects);
231  debris = element.GetAttributeBool("debris", false);
232 
233  playTinnitus = element.GetAttributeBool("playtinnitus", showEffects);
234 
235  applyFireEffects = element.GetAttributeBool("applyfireeffects", flames && showEffects);
236  ignoreFireEffectsForTags = element.GetAttributeIdentifierArray("ignorefireeffectsfortags", Array.Empty<Identifier>());
237 
238  IgnoreCover = element.GetAttributeBool("ignorecover", false);
239  OnlyInside = element.GetAttributeBool("onlyinside", false);
240  OnlyOutside = element.GetAttributeBool("onlyoutside", false);
241 
242  flash = element.GetAttributeBool("flash", showEffects);
243  flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
244  if (element.GetAttribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
245  flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow);
246 
247  PlayDamageSounds = element.GetAttributeBool(nameof(PlayDamageSounds), false);
248 
249  EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
250  BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f);
251 
252  itemRepairStrength = element.GetAttributeFloat("itemrepairstrength", 0.0f);
253 
254  decal = element.GetAttributeString("decal", "");
255  decalSize = element.GetAttributeFloat(1.0f, "decalSize", "decalsize");
256 
257  CameraShake = element.GetAttributeFloat("camerashake", showEffects ? Attack.Range * 0.1f : 0f);
258  CameraShakeRange = element.GetAttributeFloat("camerashakerange", showEffects ? Attack.Range : 0f);
259 
260  screenColorRange = element.GetAttributeFloat("screencolorrange", showEffects ? Attack.Range * 0.1f : 0f);
261  screenColor = element.GetAttributeColor("screencolor", Color.Transparent);
262  screenColorDuration = element.GetAttributeFloat("screencolorduration", 0.1f);
263 
264  }
265 
266  public void DisableParticles()
267  {
268  sparks = false;
269  shockwave = false;
270  smoke = false;
271  flash = false;
272  debris = false;
273  flames = false;
274  underwaterBubble = false;
275  }
276 
277  public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null)
278  {
279  Hull hull = Hull.FindHull(worldPosition);
280  ExplodeProjSpecific(worldPosition, hull);
281 
282  if (hull != null && !string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f)
283  {
284  hull.AddDecal(decal, worldPosition, decalSize, isNetworkEvent: false);
285  }
286 
287  Attack.DamageMultiplier = 1.0f;
288  float displayRange = Attack.Range;
289  if (damageSource is Item sourceItem)
290  {
291  var launcher = sourceItem.GetComponent<Projectile>()?.Launcher;
292  displayRange *=
293  1.0f
294  + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius)
295  + (launcher?.GetQualityModifier(Quality.StatType.ExplosionRadius) ?? 0);
297  1.0f
298  + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage)
299  + (launcher?.GetQualityModifier(Quality.StatType.ExplosionDamage) ?? 0);
300  Attack.SourceItem ??= sourceItem;
301  }
302 
303  if (attacker is not null)
304  {
305  displayRange *= 1f + attacker.GetStatValue(StatTypes.ExplosionRadiusMultiplier);
306  Attack.DamageMultiplier *= 1f + attacker.GetStatValue(StatTypes.ExplosionDamageMultiplier);
307  }
308 
309  Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
310  float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
311  GameMain.GameScreen.Cam.Shake = CameraShake * Math.Max((CameraShakeRange - cameraDist) / CameraShakeRange, 0.0f);
312 #if CLIENT
313  if (screenColor != Color.Transparent)
314  {
315  Color flashColor = Color.Lerp(Color.Transparent, screenColor, Math.Max((screenColorRange - cameraDist) / screenColorRange, 0.0f));
316  Screen.Selected.ColorFade(flashColor, Color.Transparent, screenColorDuration);
317  }
318  foreach (Item item in Item.ItemList)
319  {
320  item.GetComponent<Sonar>()?.RegisterExplosion(this, worldPosition);
321  }
322 #endif
323 
324  if (displayRange < 0.1f) { return; }
325 
326  if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f))
327  {
328  RangedStructureDamage(worldPosition, displayRange,
330  Attack.GetLevelWallDamage(1.0f),
331  attacker, IgnoredSubmarines,
335  }
336 
337  if (BallastFloraDamage > 0.0f)
338  {
339  RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker, DistanceFalloff);
340  }
341 
342  if (EmpStrength > 0.0f)
343  {
344  float displayRangeSqr = displayRange * displayRange;
345  foreach (Item item in Item.ItemList)
346  {
347  float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
348  if (distSqr > displayRangeSqr) { continue; }
349  float distFactor = DistanceFalloff ? CalculateDistanceFactor(distSqr, displayRange) : 1.0f;
350 
351  //damage repairable power-consuming items
352  var powered = item.GetComponent<Powered>();
353  if (powered == null || !powered.VulnerableToEMP) { continue; }
354  if (item.Repairables.Any())
355  {
356  item.Condition -= item.MaxCondition * EmpStrength * distFactor;
357  }
358 
359  var lightComponent = item.GetComponent<LightComponent>();
360  if (lightComponent != null)
361  {
362  //multiply by 10 to make the effect more noticeable
363  //(a strength of 1 is already enough to kill power and shut down the lights, but we want weaker EMPs to make the lights flicker noticeably)
364  lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor * 10.0f, 10.0f);
365  }
366 
367  //discharge batteries
368  var powerContainer = item.GetComponent<PowerContainer>();
369  if (powerContainer != null)
370  {
371  powerContainer.Charge -= powerContainer.GetCapacity() * EmpStrength * distFactor;
372  }
373  }
374  static float CalculateDistanceFactor(float distSqr, float displayRange) => 1.0f - MathF.Sqrt(distSqr) / displayRange;
375  }
376 
377  if (itemRepairStrength > 0.0f)
378  {
379  float displayRangeSqr = displayRange * displayRange;
380  foreach (Item item in Item.ItemList)
381  {
382  float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
383  if (distSqr > displayRangeSqr) { continue; }
384 
385  float distFactor =
386  DistanceFalloff ?
387  1.0f - (float)Math.Sqrt(distSqr) / displayRange :
388  1.0f;
389  //repair repairable items
390  if (item.Repairables.Any())
391  {
392  item.Condition += itemRepairStrength * distFactor;
393  }
394  }
395  }
396 
397  if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && Attack.Afflictions.None())
398  {
399  return;
400  }
401 
402  DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
403 
404  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
405  {
406  foreach (Item item in Item.ItemList)
407  {
408  if (item.Condition <= 0.0f) { continue; }
409  float dist = Vector2.Distance(item.WorldPosition, worldPosition);
410  float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
411  dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
412  if (dist > displayRange) { continue; }
413 
414  if (dist < displayRange * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
415  {
416  //don't apply OnFire effects if the item is inside a fireproof container
417  //(or if it's inside a container that's inside a fireproof container, etc)
418  Item container = item.Container;
419  bool fireProof = false;
420  while (container != null)
421  {
422  if (container.FireProof)
423  {
424  fireProof = true;
425  break;
426  }
427  container = container.Container;
428  }
429  if (!fireProof)
430  {
431  item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
432  if (item.Condition <= 0.0f && GameMain.NetworkMember is { IsServer: true })
433  {
434  GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFire));
435  }
436  }
437  }
438 
439  if (item.Prefab.DamagedByExplosions && !item.Indestructible)
440  {
441  float distFactor =
442  DistanceFalloff ?
443  1.0f - dist / displayRange :
444  1.0f;
445  float damageAmount = Attack.GetItemDamage(1.0f, item.Prefab.ExplosionDamageMultiplier);
446 
447  Vector2 explosionPos = worldPosition;
448  if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }
449 
450  damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition, IgnoredCover);
451  item.Condition -= damageAmount * distFactor;
452  }
453  }
454  }
455  }
456 
457  partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
458 
459  private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
460  {
461  if (attack.Range <= 0.0f) { return; }
462 
463  //long range for the broad distance check, because large characters may still be in range even if their collider isn't
464  float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f);
465 
466  foreach (Character c in Character.CharacterList)
467  {
468  if (attack.OnlyHumans && !c.IsHuman) { continue; }
469  if (IgnoredCharacters.Contains(c)) { continue; }
470 
471  if (!c.Enabled ||
472  Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange ||
473  Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange)
474  {
475  continue;
476  }
477 
478  if (OnlyInside && c.Submarine == null)
479  {
480  continue;
481  }
482  else if (OnlyOutside && c.Submarine != null)
483  {
484  continue;
485  }
486 
487  Vector2 explosionPos = worldPosition;
488  if (c.Submarine != null) { explosionPos -= c.Submarine.Position; }
489 
490  Hull hull = Hull.FindHull(explosionPos, null, false);
491  bool underWater = hull == null || explosionPos.Y < hull.Surface;
492 
493  explosionPos = ConvertUnits.ToSimUnits(explosionPos);
494 
495  Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();
496  Dictionary<Limb, float> damages = new Dictionary<Limb, float>();
497  List<Affliction> modifiedAfflictions = new List<Affliction>();
498 
499  Limb closestLimb = null;
500  float closestDistFactor = 0;
501  foreach (Limb limb in c.AnimController.Limbs)
502  {
503  if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; }
504 
505  float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
506 
507  //calculate distance from the "outer surface" of the physics body
508  //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose
509  float limbRadius = limb.body.GetMaxExtent();
510  dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
511 
512  if (dist > attack.Range) { continue; }
513 
514  float distFactor =
515  DistanceFalloff ?
516  1.0f - dist / attack.Range :
517  1.0f;
518 
519  //solid obstacles between the explosion and the limb reduce the effect of the explosion
520  if (!IgnoreCover)
521  {
522  distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition, IgnoredCover);
523  }
524  if (distFactor > 0)
525  {
526  distFactors.Add(limb, distFactor);
527  if (distFactor > closestDistFactor)
528  {
529  closestLimb = limb;
530  closestDistFactor = distFactor;
531  }
532  }
533  }
534 
535  foreach (Limb limb in distFactors.Keys)
536  {
537  if (!distFactors.TryGetValue(limb, out float distFactor)) { continue; }
538  modifiedAfflictions.Clear();
539  foreach (Affliction affliction in attack.Afflictions.Keys)
540  {
541  float dmgMultiplier = distFactor;
542  if (affliction.DivideByLimbCount)
543  {
544  float limbCountFactor = distFactors.Count;
545  if (affliction.Prefab.LimbSpecific && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
546  {
547  // Shouldn't go above 15, or the damage can be unexpectedly low -> doesn't break armor
548  // Effectively this makes large explosions more effective against large creatures (because more limbs are affected), but I don't think that's necessarily a bad thing.
549  limbCountFactor = Math.Min(distFactors.Count, 15);
550  }
551  dmgMultiplier /= limbCountFactor;
552  }
553  modifiedAfflictions.Add(affliction.CreateMultiplied(dmgMultiplier, affliction));
554  }
555  c.LastDamageSource = damageSource;
556  if (attacker == null)
557  {
558  if (damageSource is Item item)
559  {
560  attacker = item.GetComponent<Projectile>()?.User;
561  attacker ??= item.GetComponent<MeleeWeapon>()?.User;
562  }
563  }
564 
565  if (attack.Afflictions.Any() || attack.Stun > 0.0f)
566  {
567  if (!attack.OnlyHumans || c.IsHuman)
568  {
569  AbilityAttackData attackData = new AbilityAttackData(Attack, c, attacker);
570  if (attackData.Afflictions != null)
571  {
572  modifiedAfflictions.AddRange(attackData.Afflictions);
573  }
574 
575  //use a position slightly from the limb's position towards the explosion
576  //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
577  Vector2 dir = worldPosition - limb.WorldPosition;
578  Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
579 
580  //only play the damage sound on the closest limb (playing it on all just sounds like a mess)
581  bool playSound = PlayDamageSounds && limb == closestLimb;
582 
583  AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, playSound: playSound, attacker: attacker, damageMultiplier: attack.DamageMultiplier * attackData.DamageMultiplier);
584  damages.Add(limb, attackResult.Damage);
585  }
586  }
587 
588  if (attack.StatusEffects != null && attack.StatusEffects.Any())
589  {
590  attack.SetUser(attacker);
591  var statusEffectTargets = new List<ISerializableEntity>();
592  foreach (StatusEffect statusEffect in attack.StatusEffects)
593  {
594  statusEffectTargets.Clear();
595  if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) { statusEffectTargets.Add(c); }
596  if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb)) { statusEffectTargets.Add(limb); }
597  statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets);
598  statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets);
599  statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets);
600  }
601  }
602 
603  if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f))
604  {
605  Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition);
606  if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); }
607  Vector2 impulse = limbDiff * distFactor * force;
608  Vector2 impulsePoint = limb.SimPosition - limbDiff * limb.body.GetMaxExtent();
609  limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f);
610  }
611  }
612 
613  if (c == Character.Controlled && !c.IsDead && playTinnitus)
614  {
615  Limb head = c.AnimController.GetLimb(LimbType.Head);
616  if (head != null && damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor))
617  {
618  PlayTinnitusProjSpecific(headFactor);
619  }
620  }
621 
622  //sever joints
623  if (attack.SeverLimbsProbability > 0.0f)
624  {
625  foreach (Limb limb in c.AnimController.Limbs)
626  {
627  if (limb.character.Removed || limb.Removed) { continue; }
628  if (limb.IsSevered) { continue; }
629  if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; }
630  if (distFactors.TryGetValue(limb, out float distFactor))
631  {
632  if (damages.TryGetValue(limb, out float damage))
633  {
634  c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true, attacker: attacker);
635  }
636  }
637  }
638  }
639  }
640  }
641 
642  private static readonly Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
646  public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable<Submarine> ignoredSubmarines = null,
647  bool emitWallDamageParticles = true,
648  bool createWallDamageProjectiles = false,
649  bool distanceFalloff = true)
650  {
651  float dist = 600.0f;
652  damagedStructures.Clear();
653  foreach (Structure structure in Structure.WallList)
654  {
655  if (ignoredSubmarines != null && structure.Submarine != null && ignoredSubmarines.Contains(structure.Submarine)) { continue; }
656 
657  if (structure.HasBody &&
658  !structure.IsPlatform &&
659  Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f)
660  {
661  for (int i = 0; i < structure.SectionCount; i++)
662  {
663  float distFactor =
664  distanceFalloff ?
665  1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange) :
666  1.0f;
667  if (distFactor <= 0.0f) { continue; }
668 
669  structure.AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles, createWallDamageProjectiles);
670 
671  if (damagedStructures.ContainsKey(structure))
672  {
673  damagedStructures[structure] += damage * distFactor;
674  }
675  else
676  {
677  damagedStructures.Add(structure, damage * distFactor);
678  }
679  }
680  }
681  }
682 
683  if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
684  {
685  if (Level.Loaded?.LevelObjectManager != null)
686  {
687  foreach (var levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(worldPosition, worldRange))
688  {
689  if (levelObject.Prefab.TakeLevelWallDamage)
690  {
691  float distFactor = 1.0f - (Vector2.Distance(levelObject.WorldPosition, worldPosition) / worldRange);
692  if (distFactor <= 0.0f) { continue; }
693  levelObject.AddDamage(levelWallDamage * distFactor, 1.0f, null);
694  }
695  }
696  }
697 
698  for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
699  {
700  if (Level.Loaded.ExtraWalls[i] is not DestructibleLevelWall destructibleWall) { continue; }
701 
702  bool inRange = false;
703  foreach (var cell in destructibleWall.Cells)
704  {
705  if (cell.IsPointInside(worldPosition))
706  {
707  inRange = true;
708  break;
709  }
710  foreach (var edge in cell.Edges)
711  {
712  if (MathUtils.LineSegmentToPointDistanceSquared((edge.Point1 + cell.Translation).ToPoint(), (edge.Point2 + cell.Translation).ToPoint(), worldPosition.ToPoint()) < worldRange * worldRange)
713  {
714  inRange = true;
715  break;
716  }
717  }
718  if (inRange) { break; }
719  }
720  if (inRange)
721  {
722  destructibleWall.AddDamage(levelWallDamage, worldPosition);
723  }
724  }
725  }
726 
727  return damagedStructures;
728  }
729 
730  public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null, bool distanceFalloff = true)
731  {
732  List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();
733 
734  foreach (Hull hull in Hull.HullList)
735  {
736  if (hull.BallastFlora != null) { ballastFlorae.Add(hull.BallastFlora); }
737  }
738 
739  foreach (BallastFloraBehavior ballastFlora in ballastFlorae)
740  {
741  float resistanceMuliplier = ballastFlora.HasBrokenThrough ? 1f : 1f - ballastFlora.ExplosionResistance;
742  ballastFlora.Branches.ForEachMod(branch =>
743  {
744  Vector2 branchWorldPos = ballastFlora.GetWorldPosition() + branch.Position;
745  float branchDist = Vector2.Distance(branchWorldPos, worldPosition);
746  if (branchDist < worldRange)
747  {
748  float distFactor =
749  distanceFalloff ?
750  1.0f - (branchDist / worldRange) :
751  1.0f;
752  if (distFactor <= 0.0f) { return; }
753 
754  Vector2 explosionPos = worldPosition;
755  Vector2 branchPos = branchWorldPos;
756  if (ballastFlora.Parent?.Submarine != null)
757  {
758  explosionPos -= ballastFlora.Parent.Submarine.Position;
759  branchPos -= ballastFlora.Parent.Submarine.Position;
760  }
761  distFactor *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, ConvertUnits.ToSimUnits(branchPos));
762  ballastFlora.DamageBranch(branch, damage * distFactor * resistanceMuliplier, BallastFloraBehavior.AttackType.Explosives, attacker);
763  }
764  });
765  }
766  }
767 
768  private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos, IEnumerable<Structure> ignoredCover = null)
769  {
770  float damageMultiplier = 1.0f;
771  var obstacles = Submarine.PickBodies(targetSimPos, explosionSimPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
772  foreach (var body in obstacles)
773  {
774  if (body.UserData is Item item)
775  {
776  var door = item.GetComponent<Door>();
777  if (door != null && !door.IsOpen && !door.IsBroken) { damageMultiplier *= 0.01f; }
778  }
779  else if (body.UserData is Structure structure)
780  {
781  if (ignoredCover != null)
782  {
783  if (ignoredCover.Contains(structure)) { continue; }
784  }
785  int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world: true, clamp: true);
786  if (structure.SectionBodyDisabled(sectionIndex))
787  {
788  continue;
789  }
790  else if (structure.SectionIsLeaking(sectionIndex))
791  {
792  damageMultiplier *= 0.1f;
793  }
794  else
795  {
796  damageMultiplier *= 0.01f;
797  }
798  }
799  else
800  {
801  damageMultiplier *= 0.1f;
802  }
803  }
804  return damageMultiplier;
805  }
806 
807  static partial void PlayTinnitusProjSpecific(float volume);
808  }
809 }
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float DamageMultiplier
Used for multiplying all the damage.
readonly Dictionary< Affliction, XElement > Afflictions
float GetItemDamage(float deltaTime, float multiplier=1)
Vector2 Position
Definition: Camera.cs:398
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
Color GetAttributeColor(string key, in Color def)
float GetAttributeFloat(string key, float def)
bool GetAttributeBool(string key, bool def)
XAttribute? GetAttribute(string name)
Submarine Submarine
Definition: Entity.cs:53
Explosions are area of effect attacks that can damage characters, items and structures.
bool DistanceFalloff
Does the damage from the explosion decrease with distance from the origin of the explosion?
Explosion(ContentXElement element, string parentDebugName)
float BallastFloraDamage
How much damage the explosion does to ballast flora.
static Dictionary< Structure, float > RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker=null, IEnumerable< Submarine > ignoredSubmarines=null, bool emitWallDamageParticles=true, bool createWallDamageProjectiles=false, bool distanceFalloff=true)
Returns a dictionary where the keys are the structures that took damage and the values are the amount...
float CameraShake
Intensity of the screen shake effect.
bool IgnoreCover
When set to true, the explosion don't deal less damage when the target is behind a solid object.
float EmpStrength
Strength of the EMP effect created by the explosion.
readonly HashSet< Submarine > IgnoredSubmarines
float CameraShakeRange
How far away does the camera shake effect reach.
readonly HashSet< Character > IgnoredCharacters
void Explode(Vector2 worldPosition, Entity damageSource, Character attacker=null)
Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength=0.0f, float ballastFloraStrength=0.0f)
bool PlayDamageSounds
Should the normal damage sounds be played when the explosion damages something. Usually disabled.
IEnumerable< Structure > IgnoredCover
Structures that don't count as "cover" that reduces damage from the explosion. Only relevant if Ignor...
static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker=null, bool distanceFalloff=true)
bool OnlyOutside
Whether the explosion only affects characters outside a submarine.
bool OnlyInside
Whether the explosion only affects characters inside a submarine.
static GameScreen GameScreen
Definition: GameMain.cs:52
static NetworkMember NetworkMember
Definition: GameMain.cs:190
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
static readonly List< Hull > HullList
bool?? Indestructible
Per-instance value - if not set, the value of the prefab is used.
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb limb=null, Entity useTarget=null, bool isNetworkEvent=false, Vector2? worldPosition=null)
Executes all StatusEffects of the specified type. Note that condition checks are ignored here: that s...
static readonly List< Item > ItemList
void ColorFade(Color from, Color to, float duration)
Vector2 SectionPosition(int sectionIndex, bool world=false)
void AddDamage(int sectionIndex, float damage, Character attacker=null, bool emitParticles=true, bool createWallDamageProjectiles=false)
static IEnumerable< Body > PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
Returns a list of physics bodies the ray intersects with, sorted according to distance (the closest b...
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180