6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
28 private readonly
float force;
53 private readonly Color screenColor;
63 private readonly
float screenColorRange;
68 private readonly
float screenColorDuration;
78 private bool shockwave;
103 private bool underwaterBubble;
108 private readonly Color flashColor;
113 private readonly
bool playTinnitus;
123 private readonly
bool applyFireEffects;
128 private readonly Identifier[] ignoreFireEffectsForTags;
148 private readonly
float flashDuration;
153 private readonly
float? flashRange;
159 private readonly
string decal;
164 private readonly
float decalSize;
184 private readonly
float itemRepairStrength;
200 public Explosion(
float range,
float force,
float damage,
float structureDamage,
float itemDamage,
float empStrength = 0.0f,
float ballastFloraStrength = 0.0f)
202 Attack =
new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, Math.Min(range, 1000000))
204 SeverLimbsProbability = 1.0f
214 underwaterBubble =
true;
215 ignoreFireEffectsForTags = Array.Empty<Identifier>();
220 Attack =
new Attack(element, parentDebugName +
", Explosion");
229 underwaterBubble = element.
GetAttributeBool(
"underwaterbubble", showEffects);
235 applyFireEffects = element.
GetAttributeBool(
"applyfireeffects", flames && showEffects);
274 underwaterBubble =
false;
280 ExplodeProjSpecific(worldPosition, hull);
282 if (hull !=
null && !
string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f)
284 hull.
AddDecal(decal, worldPosition, decalSize, isNetworkEvent:
false);
289 if (damageSource is
Item sourceItem)
291 var launcher = sourceItem.GetComponent<
Projectile>()?.Launcher;
295 + (launcher?.GetQualityModifier(
Quality.
StatType.ExplosionRadius) ?? 0);
299 + (launcher?.GetQualityModifier(
Quality.
StatType.ExplosionDamage) ?? 0);
303 if (attacker is not
null)
305 displayRange *= 1f + attacker.GetStatValue(
StatTypes.ExplosionRadiusMultiplier);
310 float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
313 if (screenColor != Color.Transparent)
315 Color flashColor = Color.Lerp(Color.Transparent, screenColor, Math.Max((screenColorRange - cameraDist) / screenColorRange, 0.0f));
320 item.GetComponent<
Sonar>()?.RegisterExplosion(
this, worldPosition);
324 if (displayRange < 0.1f) {
return; }
344 float displayRangeSqr = displayRange * displayRange;
347 float distSqr = Vector2.DistanceSquared(item.
WorldPosition, worldPosition);
348 if (distSqr > displayRangeSqr) {
continue; }
349 float distFactor =
DistanceFalloff ? CalculateDistanceFactor(distSqr, displayRange) : 1.0f;
352 var powered = item.GetComponent<
Powered>();
353 if (powered ==
null || !powered.VulnerableToEMP) {
continue; }
360 if (lightComponent !=
null)
369 if (powerContainer !=
null)
371 powerContainer.
Charge -= powerContainer.GetCapacity() *
EmpStrength * distFactor;
374 static float CalculateDistanceFactor(
float distSqr,
float displayRange) => 1.0f - MathF.Sqrt(distSqr) / displayRange;
377 if (itemRepairStrength > 0.0f)
379 float displayRangeSqr = displayRange * displayRange;
382 float distSqr = Vector2.DistanceSquared(item.
WorldPosition, worldPosition);
383 if (distSqr > displayRangeSqr) {
continue; }
387 1.0f - (float)Math.Sqrt(distSqr) / displayRange :
392 item.
Condition += itemRepairStrength * distFactor;
402 DamageCharacters(worldPosition,
Attack, force, damageSource, attacker);
408 if (item.
Condition <= 0.0f) {
continue; }
409 float dist = Vector2.Distance(item.
WorldPosition, worldPosition);
411 dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
412 if (dist > displayRange) {
continue; }
414 if (dist < displayRange * 0.5f && applyFireEffects && !item.
FireProof && ignoreFireEffectsForTags.None(t => item.
HasTag(t)))
419 bool fireProof =
false;
420 while (container !=
null)
443 1.0f - dist / displayRange :
447 Vector2 explosionPos = worldPosition;
450 damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.
SimPosition,
IgnoredCover);
451 item.
Condition -= damageAmount * distFactor;
457 partial
void ExplodeProjSpecific(Vector2 worldPosition,
Hull hull);
459 private void DamageCharacters(Vector2 worldPosition,
Attack attack,
float force,
Entity damageSource,
Character attacker)
461 if (attack.
Range <= 0.0f) {
return; }
464 float broadRange = Math.Max(attack.
Range * 10.0f, 10000.0f);
466 foreach (Character c
in Character.CharacterList)
468 if (attack.
OnlyHumans && !c.IsHuman) {
continue; }
472 Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange ||
473 Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange)
487 Vector2 explosionPos = worldPosition;
488 if (c.Submarine !=
null) { explosionPos -= c.Submarine.Position; }
490 Hull hull = Hull.FindHull(explosionPos,
null,
false);
491 bool underWater = hull ==
null || explosionPos.Y < hull.Surface;
493 explosionPos = ConvertUnits.ToSimUnits(explosionPos);
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>();
499 Limb closestLimb =
null;
500 float closestDistFactor = 0;
501 foreach (Limb limb
in c.AnimController.Limbs)
503 if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) {
continue; }
505 float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
509 float limbRadius = limb.body.GetMaxExtent();
510 dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
512 if (dist > attack.
Range) {
continue; }
516 1.0f - dist / attack.
Range :
522 distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition,
IgnoredCover);
526 distFactors.Add(limb, distFactor);
527 if (distFactor > closestDistFactor)
530 closestDistFactor = distFactor;
535 foreach (Limb limb
in distFactors.Keys)
537 if (!distFactors.TryGetValue(limb, out
float distFactor)) {
continue; }
538 modifiedAfflictions.Clear();
539 foreach (Affliction affliction
in attack.
Afflictions.Keys)
541 float dmgMultiplier = distFactor;
542 if (affliction.DivideByLimbCount)
544 float limbCountFactor = distFactors.Count;
545 if (affliction.Prefab.LimbSpecific && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
549 limbCountFactor = Math.Min(distFactors.Count, 15);
551 dmgMultiplier /= limbCountFactor;
553 modifiedAfflictions.Add(affliction.CreateMultiplied(dmgMultiplier, affliction));
555 c.LastDamageSource = damageSource;
556 if (attacker ==
null)
558 if (damageSource is Item item)
560 attacker = item.GetComponent<
Projectile>()?.User;
561 attacker ??= item.GetComponent<
MeleeWeapon>()?.User;
569 AbilityAttackData attackData =
new AbilityAttackData(
Attack, c, attacker);
570 if (attackData.Afflictions !=
null)
572 modifiedAfflictions.AddRange(attackData.Afflictions);
577 Vector2 dir = worldPosition - limb.WorldPosition;
578 Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
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);
591 var statusEffectTargets =
new List<ISerializableEntity>();
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);
603 if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f))
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);
613 if (c ==
Character.Controlled && !c.IsDead && playTinnitus)
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))
618 PlayTinnitusProjSpecific(headFactor);
625 foreach (Limb limb
in c.AnimController.Limbs)
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))
632 if (damages.TryGetValue(limb, out
float damage))
634 c.TrySeverLimbJoints(limb, attack.
SeverLimbsProbability * distFactor, damage, allowBeheading:
true, attacker: attacker);
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)
652 damagedStructures.Clear();
655 if (ignoredSubmarines !=
null && structure.
Submarine !=
null && ignoredSubmarines.Contains(structure.
Submarine)) {
continue; }
659 Vector2.Distance(structure.
WorldPosition, worldPosition) < dist * 3.0f)
665 1.0f - (Vector2.Distance(structure.
SectionPosition(i,
true), worldPosition) / worldRange) :
667 if (distFactor <= 0.0f) {
continue; }
669 structure.
AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles, createWallDamageProjectiles);
671 if (damagedStructures.ContainsKey(structure))
673 damagedStructures[structure] += damage * distFactor;
677 damagedStructures.Add(structure, damage * distFactor);
683 if (
Level.
Loaded !=
null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
689 if (levelObject.Prefab.TakeLevelWallDamage)
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);
702 bool inRange =
false;
703 foreach (var cell
in destructibleWall.Cells)
705 if (cell.IsPointInside(worldPosition))
710 foreach (var edge
in cell.Edges)
712 if (MathUtils.LineSegmentToPointDistanceSquared((edge.Point1 + cell.Translation).ToPoint(), (edge.Point2 + cell.Translation).ToPoint(), worldPosition.ToPoint()) < worldRange * worldRange)
718 if (inRange) {
break; }
722 destructibleWall.AddDamage(levelWallDamage, worldPosition);
727 return damagedStructures;
732 List<BallastFloraBehavior> ballastFlorae =
new List<BallastFloraBehavior>();
739 foreach (BallastFloraBehavior ballastFlora
in ballastFlorae)
741 float resistanceMuliplier = ballastFlora.HasBrokenThrough ? 1f : 1f - ballastFlora.ExplosionResistance;
742 ballastFlora.Branches.ForEachMod(branch =>
744 Vector2 branchWorldPos = ballastFlora.GetWorldPosition() + branch.Position;
745 float branchDist = Vector2.Distance(branchWorldPos, worldPosition);
746 if (branchDist < worldRange)
750 1.0f - (branchDist / worldRange) :
752 if (distFactor <= 0.0f) {
return; }
754 Vector2 explosionPos = worldPosition;
755 Vector2 branchPos = branchWorldPos;
756 if (ballastFlora.Parent?.Submarine !=
null)
758 explosionPos -= ballastFlora.Parent.Submarine.Position;
759 branchPos -= ballastFlora.Parent.Submarine.Position;
761 distFactor *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, ConvertUnits.ToSimUnits(branchPos));
762 ballastFlora.DamageBranch(branch, damage * distFactor * resistanceMuliplier, BallastFloraBehavior.AttackType.Explosives, attacker);
768 private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos, IEnumerable<Structure> ignoredCover =
null)
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)
774 if (body.UserData is
Item item)
776 var door = item.GetComponent<
Door>();
777 if (door !=
null && !door.IsOpen && !door.IsBroken) { damageMultiplier *= 0.01f; }
779 else if (body.UserData is Structure structure)
781 if (ignoredCover !=
null)
783 if (ignoredCover.Contains(structure)) {
continue; }
785 int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world:
true, clamp:
true);
786 if (structure.SectionBodyDisabled(sectionIndex))
790 else if (structure.SectionIsLeaking(sectionIndex))
792 damageMultiplier *= 0.1f;
796 damageMultiplier *= 0.01f;
801 damageMultiplier *= 0.1f;
804 return damageMultiplier;
807 static partial
void PlayTinnitusProjSpecific(
float volume);
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float GetStructureDamage(float deltaTime)
bool EmitStructureDamageParticles
float GetLevelWallDamage(float deltaTime)
float DamageMultiplier
Used for multiplying all the damage.
bool CreateWallDamageProjectiles
void SetUser(Character user)
IEnumerable< StatusEffect > StatusEffects
readonly Dictionary< Affliction, XElement > Afflictions
float GetItemDamage(float deltaTime, float multiplier=1)
float SeverLimbsProbability
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)
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
static NetworkMember NetworkMember
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
BallastFloraBehavior BallastFlora
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
override Vector2? SimPosition
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...
List< Repairable > Repairables
bool HasTag(Identifier tag)
static readonly List< Item > ItemList
float ExplosionDamageMultiplier
float TemporaryFlickerTimer
LevelObjectManager LevelObjectManager
List< LevelWall > ExtraWalls
IEnumerable< LevelObject > GetAllObjects()
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 List< Structure > WallList
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...
override Vector2? Position
ActionType
ActionTypes define when a StatusEffect is executed.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.