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);
276 underwaterBubble =
false;
282 ExplodeProjSpecific(worldPosition, hull);
284 if (hull !=
null && !
string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f)
286 hull.
AddDecal(decal, worldPosition, decalSize, isNetworkEvent:
false);
291 if (damageSource is
Item sourceItem)
293 var launcher = sourceItem.GetComponent<
Projectile>()?.Launcher;
297 + (launcher?.GetQualityModifier(
Quality.
StatType.ExplosionRadius) ?? 0);
301 + (launcher?.GetQualityModifier(
Quality.
StatType.ExplosionDamage) ?? 0);
305 if (attacker is not
null)
307 displayRange *= 1f + attacker.GetStatValue(
StatTypes.ExplosionRadiusMultiplier);
312 float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
315 if (screenColor != Color.Transparent)
317 Color flashColor = Color.Lerp(Color.Transparent, screenColor, Math.Max((screenColorRange - cameraDist) / screenColorRange, 0.0f));
318 Screen.
Selected.ColorFade(flashColor, Color.Transparent, screenColorDuration);
322 item.GetComponent<
Sonar>()?.RegisterExplosion(
this, worldPosition);
326 if (displayRange < 0.1f) {
return; }
346 float displayRangeSqr = displayRange * displayRange;
349 float distSqr = Vector2.DistanceSquared(item.
WorldPosition, worldPosition);
350 if (distSqr > displayRangeSqr) {
continue; }
351 float distFactor =
DistanceFalloff ? CalculateDistanceFactor(distSqr, displayRange) : 1.0f;
354 var powered = item.GetComponent<
Powered>();
355 if (powered ==
null || !powered.VulnerableToEMP) {
continue; }
362 if (lightComponent !=
null)
371 if (powerContainer !=
null)
373 powerContainer.
Charge -= powerContainer.GetCapacity() *
EmpStrength * distFactor;
376 static float CalculateDistanceFactor(
float distSqr,
float displayRange) => 1.0f - MathF.Sqrt(distSqr) / displayRange;
379 if (itemRepairStrength > 0.0f)
381 float displayRangeSqr = displayRange * displayRange;
384 float distSqr = Vector2.DistanceSquared(item.
WorldPosition, worldPosition);
385 if (distSqr > displayRangeSqr) {
continue; }
389 1.0f - (float)Math.Sqrt(distSqr) / displayRange :
394 item.
Condition += itemRepairStrength * distFactor;
404 DamageCharacters(worldPosition,
Attack, force, damageSource, attacker);
410 if (item.
Condition <= 0.0f) {
continue; }
411 float dist = Vector2.Distance(item.
WorldPosition, worldPosition);
413 dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
414 if (dist > displayRange) {
continue; }
416 if (dist < displayRange * 0.5f && applyFireEffects && !item.
FireProof && ignoreFireEffectsForTags.None(t => item.
HasTag(t)))
421 bool fireProof =
false;
422 while (container !=
null)
448 1.0f - dist / displayRange :
452 Vector2 explosionPos = worldPosition;
455 damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.
SimPosition,
IgnoredCover);
456 item.
Condition -= damageAmount * distFactor;
463 partial
void ExplodeProjSpecific(Vector2 worldPosition,
Hull hull);
465 private void DamageCharacters(Vector2 worldPosition,
Attack attack,
float force,
Entity damageSource,
Character attacker)
467 if (attack.
Range <= 0.0f) {
return; }
470 float broadRange = Math.Max(attack.
Range * 10.0f, 10000.0f);
472 foreach (Character c
in Character.CharacterList)
474 if (attack.
OnlyHumans && !c.IsHuman) {
continue; }
478 Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange ||
479 Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange)
493 Vector2 explosionPos = worldPosition;
494 if (c.Submarine !=
null) { explosionPos -= c.Submarine.Position; }
496 Hull hull = Hull.FindHull(explosionPos,
null,
false);
497 bool underWater = hull ==
null || explosionPos.Y < hull.Surface;
499 explosionPos = ConvertUnits.ToSimUnits(explosionPos);
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>();
505 Limb closestLimb =
null;
506 float closestDistFactor = 0;
507 foreach (Limb limb
in c.AnimController.Limbs)
509 if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) {
continue; }
511 float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
515 float limbRadius = limb.body.GetMaxExtent();
516 dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
518 if (dist > attack.
Range) {
continue; }
522 1.0f - dist / attack.
Range :
528 distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition,
IgnoredCover);
532 distFactors.Add(limb, distFactor);
533 if (distFactor > closestDistFactor)
536 closestDistFactor = distFactor;
541 foreach (Limb limb
in distFactors.Keys)
543 if (!distFactors.TryGetValue(limb, out
float distFactor)) {
continue; }
544 modifiedAfflictions.Clear();
545 foreach (Affliction affliction
in attack.
Afflictions.Keys)
547 float dmgMultiplier = distFactor;
548 if (affliction.DivideByLimbCount)
550 float limbCountFactor = distFactors.Count;
551 if (affliction.Prefab.LimbSpecific && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
555 limbCountFactor = Math.Min(distFactors.Count, 15);
557 dmgMultiplier /= limbCountFactor;
559 modifiedAfflictions.Add(affliction.CreateMultiplied(dmgMultiplier, affliction));
561 c.LastDamageSource = damageSource;
562 if (attacker ==
null)
564 if (damageSource is
Item item)
566 attacker = item.GetComponent<
Projectile>()?.User;
567 attacker ??= item.GetComponent<
MeleeWeapon>()?.User;
575 AbilityAttackData attackData =
new AbilityAttackData(
Attack, c, attacker);
576 if (attackData.Afflictions !=
null)
578 modifiedAfflictions.AddRange(attackData.Afflictions);
583 Vector2 dir = worldPosition - limb.WorldPosition;
584 Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
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);
597 var statusEffectTargets =
new List<ISerializableEntity>();
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);
609 if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f))
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);
619 if (c ==
Character.Controlled && !c.IsDead && playTinnitus)
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))
624 PlayTinnitusProjSpecific(headFactor);
631 foreach (Limb limb
in c.AnimController.Limbs)
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))
638 if (damages.TryGetValue(limb, out
float damage))
640 c.TrySeverLimbJoints(limb, attack.
SeverLimbsProbability * distFactor, damage, allowBeheading:
true, attacker: attacker);
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)
658 damagedStructures.Clear();
661 if (ignoredSubmarines !=
null && structure.
Submarine !=
null && ignoredSubmarines.Contains(structure.
Submarine)) {
continue; }
665 Vector2.Distance(structure.
WorldPosition, worldPosition) < dist * 3.0f)
671 1.0f - (Vector2.Distance(structure.
SectionPosition(i,
true), worldPosition) / worldRange) :
673 if (distFactor <= 0.0f) {
continue; }
675 structure.
AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles, createWallDamageProjectiles);
677 if (damagedStructures.ContainsKey(structure))
679 damagedStructures[structure] += damage * distFactor;
683 damagedStructures.Add(structure, damage * distFactor);
689 if (
Level.
Loaded !=
null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
695 if (levelObject.Prefab.TakeLevelWallDamage)
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);
708 bool inRange =
false;
709 foreach (var cell
in destructibleWall.Cells)
711 if (cell.IsPointInside(worldPosition))
716 foreach (var edge
in cell.Edges)
718 if (MathUtils.LineSegmentToPointDistanceSquared((edge.Point1 + cell.Translation).ToPoint(), (edge.Point2 + cell.Translation).ToPoint(), worldPosition.ToPoint()) < worldRange * worldRange)
724 if (inRange) {
break; }
728 destructibleWall.AddDamage(levelWallDamage, worldPosition);
733 return damagedStructures;
738 List<BallastFloraBehavior> ballastFlorae =
new List<BallastFloraBehavior>();
745 foreach (BallastFloraBehavior ballastFlora
in ballastFlorae)
747 float resistanceMuliplier = ballastFlora.HasBrokenThrough ? 1f : 1f - ballastFlora.ExplosionResistance;
748 ballastFlora.Branches.ForEachMod(branch =>
750 Vector2 branchWorldPos = ballastFlora.GetWorldPosition() + branch.Position;
751 float branchDist = Vector2.Distance(branchWorldPos, worldPosition);
752 if (branchDist < worldRange)
756 1.0f - (branchDist / worldRange) :
758 if (distFactor <= 0.0f) {
return; }
760 Vector2 explosionPos = worldPosition;
761 Vector2 branchPos = branchWorldPos;
762 if (ballastFlora.Parent?.Submarine !=
null)
764 explosionPos -= ballastFlora.Parent.Submarine.Position;
765 branchPos -= ballastFlora.Parent.Submarine.Position;
767 distFactor *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, ConvertUnits.ToSimUnits(branchPos));
768 ballastFlora.DamageBranch(branch, damage * distFactor * resistanceMuliplier, BallastFloraBehavior.AttackType.Explosives, attacker);
774 private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos, IEnumerable<Structure> ignoredCover =
null)
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)
780 if (body.UserData is
Item item)
782 var door = item.GetComponent<
Door>();
783 if (door !=
null && !door.IsOpen && !door.IsBroken) { damageMultiplier *= 0.01f; }
785 else if (body.UserData is Structure structure)
787 if (ignoredCover !=
null)
789 if (ignoredCover.Contains(structure)) {
continue; }
791 int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world:
true, clamp:
true);
792 if (structure.SectionBodyDisabled(sectionIndex))
796 else if (structure.SectionIsLeaking(sectionIndex))
798 damageMultiplier *= 0.1f;
802 damageMultiplier *= 0.01f;
807 damageMultiplier *= 0.1f;
810 return damageMultiplier;
813 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)
IEnumerable< Item > ContainedItems
static readonly List< Item > ItemList
bool DamagedByContainedItemExplosions
float ExplosionDamageMultiplier
float TemporaryFlickerTimer
LevelObjectManager LevelObjectManager
List< LevelWall > ExtraWalls
Special wall chunks that aren't part of the normal level geometry: includes things like the ocean flo...
IEnumerable< LevelObject > GetAllObjects()
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.
@ Character
Characters only