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