示例#1
0
        private void Start(GameServer server, int traitorCount)
        {
            if (server == null)
            {
                return;
            }

            List <Character> characters        = new List <Character>(); //ANYONE can be a target.
            List <Character> traitorCandidates = new List <Character>(); //Keep this to not re-pick traitors twice

            foreach (Client client in server.ConnectedClients)
            {
                if (client.Character != null)
                {
                    characters.Add(client.Character);
                    traitorCandidates.Add(client.Character);
                }
            }

            if (server.Character != null)
            {
                characters.Add(server.Character); //Add host character
                traitorCandidates.Add(server.Character);
            }

            if (characters.Count < 2)
            {
                return;
            }

            codeWords    = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
            codeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);

            while (traitorCount-- > 0)
            {
                if (traitorCandidates.Count <= 0)
                {
                    break;
                }

                int       traitorIndex     = Rand.Int(traitorCandidates.Count);
                Character traitorCharacter = traitorCandidates[traitorIndex];
                traitorCandidates.Remove(traitorCharacter);

                //Add them to the list
                traitorList.Add(new Traitor(traitorCharacter));
            }

            //Now that traitors have been decided, let's do objectives in post for deciding things like Document Exchange.
            foreach (Traitor traitor in traitorList)
            {
                Character traitorCharacter = traitor.Character;
                int       targetIndex      = Rand.Int(characters.Count);
                while (characters[targetIndex] == traitorCharacter) //Cannot target self
                {
                    targetIndex = Rand.Int(characters.Count);
                }

                Character targetCharacter = characters[targetIndex];
                traitor.TargetCharacter = targetCharacter;
                traitor.Greet(server, codeWords, codeResponse);
            }
        }
示例#2
0
        public override void Apply(ActionType type, float deltaTime, Entity entity, List<ISerializableEntity> targets, Character causecharacter = null, string identifier = "")
        {
            if (this.type != type || !HasRequiredItems(entity)) return;
            if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) return;

            //remove invalid targets
            if (targetNames != null)
            {
                targets.RemoveAll(t => !targetNames.Contains(t.Name));
                if (targets.Count == 0) return;
            }

            if (identifier == "") identifier = "statuseffect";

            DelayedListElement element = new DelayedListElement
            {
                Parent = this,
                StartTimer = delay,
                Entity = entity,
                Targets = targets,
                causecharacter = causecharacter,
                identifier = identifier
            };

            DelayList.Add(element);
        }
示例#3
0
        public Character TargetCharacter; //TODO: make a modular objective system (similar to crew missions) that allows for things OTHER than assasinations.

        public Traitor(Character character)
        {
            Character = character;
        }
示例#4
0
 public bool ShouldShowIcon(Character afflictedCharacter)
 {
     return(Strength >= (afflictedCharacter == Character.Controlled ? Prefab.ShowIconThreshold : Prefab.ShowIconToOthersThreshold));
 }
示例#5
0
        public override void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Character causecharacter = null, string identifier = "")
        {
            if (this.type != type || !HasRequiredItems(entity)) return;
            if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.Count == 1 && d.Targets[0] == target)) return;

            if (targetNames != null && !targetNames.Contains(target.Name)) return;

            if (identifier == "") identifier = "statuseffect";

            DelayedListElement element = new DelayedListElement
            {
                Parent = this,
                StartTimer = delay,
                Entity = entity,
                Targets = new List<ISerializableEntity>() { target },
                causecharacter = causecharacter,
                identifier = identifier
            };

            DelayList.Add(element);
        }
示例#6
0
 private bool IsCaptured(Character character)
 {
     return(character.LockHands && character.HasTeamChange(TerroristTeamChangeIdentifier));
 }
示例#7
0
        private void InitEscort()
        {
            characters.Clear();
            characterItems.Clear();

            WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);

            Rand.RandSync randSync = Rand.RandSync.Server;

            if (terroristChance > 0f)
            {
                // in terrorist missions, reroll characters each retry to avoid confusion as to who the terrorists are
                randSync = Rand.RandSync.Unsynced;
            }

            //if any of the escortees have a job defined, try to use a spawnpoint designated for that job
            foreach (XElement element in characterConfig.Elements())
            {
                var humanPrefab = GetHumanPrefabFromElement(element);
                if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                var jobPrefab = humanPrefab.GetJobPrefab();
                if (jobPrefab != null)
                {
                    var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub);
                    if (jobSpecificSpawnPos != null)
                    {
                        explicitStayInHullPos = jobSpecificSpawnPos;
                        break;
                    }
                }
            }

            foreach (XElement element in characterConfig.Elements())
            {
                int count = CalculateScalingEscortedCharacterCount(inMission: true);
                for (int i = 0; i < count; i++)
                {
                    Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
                    if (spawnedCharacter.AIController is HumanAIController humanAI)
                    {
                        humanAI.InitMentalStateManager();
                    }
                }
            }

            if (terroristChance > 0f)
            {
                int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
                terroristCount = Math.Clamp(terroristCount, 1, characters.Count);

                terroristCharacters.Clear();
                characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));

                terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);

#if DEBUG
                DebugConsole.AddWarning("Terrorists will trigger at range  " + Math.Sqrt(terroristDistanceSquared));
                foreach (Character character in terroristCharacters)
                {
                    DebugConsole.AddWarning(character.Name + " is a terrorist.");
                }
#endif
            }
        }
示例#8
0
 private bool Survived(Character character)
 {
     return(IsAlive(character) && character.CurrentHull != null && character.CurrentHull.Submarine == Submarine.MainSub);
 }
示例#9
0
 private bool IsAlive(Character character)
 {
     return(character != null && !character.Removed && !character.IsDead);
 }
示例#10
0
        public void Update(float deltaTime)
        {
            var character = AiController.Character;

            if (character?.Removed ?? true || character.IsDead)
            {
                return;
            }

            if (unstunY.HasValue)
            {
                if (PlayTimer > 4.0f)
                {
                    float extent = character.AnimController.MainLimb.body.GetMaxExtent();
                    if (character.SimPosition.Y < (unstunY.Value + extent * 3.0f) &&
                        character.AnimController.MainLimb.body.LinearVelocity.Y < 0.0f)
                    {
                        character.IsRagdolled = false;
                        unstunY = null;
                    }
                    else
                    {
                        character.IsRagdolled = true;
                    }
                }
                else
                {
                    character.IsRagdolled = false;
                    unstunY = null;
                }
            }

            PlayTimer -= deltaTime;

            if (GameMain.NetworkMember?.IsClient ?? false)
            {
                return;
            }
            if (Owner != null && (Owner.Removed || Owner.IsDead))
            {
                Owner = null;
            }

            Hunger    += HungerIncreaseRate * deltaTime;
            Happiness -= HappinessDecreaseRate * deltaTime;

            for (int i = 0; i < foods.Count; i++)
            {
                Food food = foods[i];
                if (Hunger >= food.HungerRange.X && Hunger <= food.HungerRange.Y)
                {
                    if (food.TargetParams == null &&
                        AiController.AIParams.TryAddNewTarget(food.Tag, AIState.Eat, food.Priority, out CharacterParams.TargetParams targetParams))
                    {
                        targetParams.IgnoreContained = food.IgnoreContained;
                        food.TargetParams            = targetParams;
                    }
                }
                else if (food.TargetParams != null)
                {
                    AiController.AIParams.RemoveTarget(food.TargetParams);
                    food.TargetParams = null;
                }
            }

            if (Hunger >= MaxHunger * 0.99f)
            {
                character.CharacterHealth.ApplyAffliction(character.AnimController.MainLimb, new Affliction(AfflictionPrefab.InternalDamage, 8.0f * deltaTime));
            }
            else if (Hunger < MaxHunger * 0.1f)
            {
                character.CharacterHealth.ReduceAffliction(null, null, 8.0f * deltaTime);
            }

            if (character.SelectedBy != null)
            {
                character.IsRagdolled = true;
                unstunY = character.SimPosition.Y;
            }

            for (int i = 0; i < itemsToProduce.Count; i++)
            {
                itemsToProduce[i].Update(this, deltaTime);
            }
        }
示例#11
0
 partial void OnHealthChangedProjSpecific(Character attacker, float damageAmount)
 {
     GameMain.Server.KarmaManager.OnStructureHealthChanged(this, attacker, damageAmount);
 }
示例#12
0
        public void Update(float deltaTime)
        {
            if (Body.FarseerBody.IsStatic)
            {
                return;
            }

            ClientUpdatePosition(deltaTime);
            if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
            {
                return;
            }

            //if outside left or right edge of the level
            if (Position.X < 0 || Position.X > Level.Loaded.Size.X)
            {
                Rectangle worldBorders = Borders;
                worldBorders.Location += MathUtils.ToPoint(Position);

                //push the sub back below the upper "barrier" of the level
                if (worldBorders.Y > Level.Loaded.Size.Y)
                {
                    Body.LinearVelocity = new Vector2(
                        Body.LinearVelocity.X,
                        Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y)));
                }
                else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos)
                {
                    Body.LinearVelocity = new Vector2(
                        Body.LinearVelocity.X,
                        Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height))));
                }
            }

            //-------------------------

            Vector2 totalForce = CalculateBuoyancy();

            if (Body.LinearVelocity.LengthSquared() > 0.0001f)
            {
                //TODO: sync current drag with clients?
                float     attachedMass = 0.0f;
                JointEdge jointEdge    = Body.FarseerBody.JointList;
                while (jointEdge != null)
                {
                    Body      otherBody = jointEdge.Joint.BodyA == Body.FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA;
                    Character character = (otherBody.UserData as Limb)?.character;
                    if (character != null)
                    {
                        attachedMass += character.Mass;
                    }

                    jointEdge = jointEdge.Next;
                }

                float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
                totalForce.X -= Math.Sign(Body.LinearVelocity.X) * Body.LinearVelocity.X * Body.LinearVelocity.X * horizontalDragCoefficient * Body.Mass;

                float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
                totalForce.Y -= Math.Sign(Body.LinearVelocity.Y) * Body.LinearVelocity.Y * Body.LinearVelocity.Y * verticalDragCoefficient * Body.Mass;
            }

            ApplyForce(totalForce);

            UpdateDepthDamage(deltaTime);
        }
示例#13
0
        public void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
        {
            List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();

            foreach (Hull hull in Hull.hullList)
            {
                if (hull.BallastFlora != null) { ballastFlorae.Add(hull.BallastFlora); }
            }

            foreach (BallastFloraBehavior ballastFlora in ballastFlorae)
            {
                float resistanceMuliplier = ballastFlora.HasBrokenThrough ? 1f : 1f - ballastFlora.ExplosionResistance; 
                ballastFlora.Branches.ForEachMod(branch =>
                {
                    Vector2 branchWorldPos = ballastFlora.GetWorldPosition() + branch.Position;
                    float branchDist = Vector2.Distance(branchWorldPos, worldPosition);
                    if (branchDist < worldRange)
                    {
                        float distFactor = 1.0f - (branchDist / worldRange);
                        if (distFactor <= 0.0f) { return; }

                        Vector2 explosionPos = worldPosition;
                        Vector2 branchPos = branchWorldPos;
                        if (ballastFlora.Parent?.Submarine != null) 
                        { 
                            explosionPos -= ballastFlora.Parent.Submarine.Position;
                            branchPos -= ballastFlora.Parent.Submarine.Position; 
                        }
                        distFactor *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, ConvertUnits.ToSimUnits(branchPos));
                        ballastFlora.DamageBranch(branch, damage * distFactor * resistanceMuliplier, BallastFloraBehavior.AttackType.Explosives, attacker);
                    }
                });
            }
        }
示例#14
0
        /// <summary>
        /// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
        /// </summary>
        public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null)
        {
            List<Structure> structureList = new List<Structure>();            
            float dist = 600.0f;
            foreach (MapEntity entity in MapEntity.mapEntityList)
            {
                if (!(entity is Structure structure)) { continue; }

                if (structure.HasBody &&
                    !structure.IsPlatform &&
                    Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f)
                {
                    structureList.Add(structure);
                }
            }

            Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
            foreach (Structure structure in structureList)
            {
                for (int i = 0; i < structure.SectionCount; i++)
                {
                    float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
                    if (distFactor <= 0.0f) continue;

                    structure.AddDamage(i, damage * distFactor, attacker);

                    if (damagedStructures.ContainsKey(structure))
                    {
                        damagedStructures[structure] += damage * distFactor;
                    }
                    else
                    {
                        damagedStructures.Add(structure, damage * distFactor);
                    }
                }
            }

            if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
            {
                for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
                {
                    if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
                    foreach (var cell in destructibleWall.Cells)
                    {
                        if (cell.IsPointInside(worldPosition))
                        {
                            destructibleWall.AddDamage(levelWallDamage, worldPosition);
                            continue;
                        }
                        foreach (var edge in cell.Edges)
                        {
                            if (!MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection))
                            {
                                continue;
                            }

                            float wallDist = Vector2.DistanceSquared(worldPosition, intersection);
                            if (wallDist < worldRange * worldRange)
                            {
                                destructibleWall.AddDamage(damage, worldPosition);
                                break;
                            }
                        }
                    }
                }
            }

            return damagedStructures;
        }
示例#15
0
        public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
        {
            if (attack.Range <= 0.0f) { return; }

            //long range for the broad distance check, because large characters may still be in range even if their collider isn't
            float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f);

            foreach (Character c in Character.CharacterList)
            {
                if (!c.Enabled || 
                    Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange ||
                    Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange)
                {
                    continue;
                }

                Vector2 explosionPos = worldPosition;
                if (c.Submarine != null) { explosionPos -= c.Submarine.Position; }

                Hull hull = Hull.FindHull(explosionPos, null, false);
                bool underWater = hull == null || explosionPos.Y < hull.Surface;

                explosionPos = ConvertUnits.ToSimUnits(explosionPos);

                Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();
                Dictionary<Limb, float> damages = new Dictionary<Limb, float>();
                List<Affliction> modifiedAfflictions = new List<Affliction>();
                foreach (Limb limb in c.AnimController.Limbs)
                {
                    if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; }

                    float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
                    
                    //calculate distance from the "outer surface" of the physics body
                    //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose
                    float limbRadius = limb.body.GetMaxExtent();
                    dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));

                    if (dist > attack.Range) { continue; }

                    float distFactor = 1.0f - dist / attack.Range;

                    //solid obstacles between the explosion and the limb reduce the effect of the explosion
                    distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
                    distFactors.Add(limb, distFactor);

                    modifiedAfflictions.Clear();
                    foreach (Affliction affliction in attack.Afflictions.Keys)
                    {
                        //previously the damage would be divided by the number of limbs (the intention was to prevent characters with more limbs taking more damage from explosions)
                        //that didn't work well on large characters like molochs and endworms: the explosions tend to only damage one or two of their limbs, and since the characters
                        //have lots of limbs, they tended to only take a fraction of the damage they should

                        //now we just divide by 10, which keeps the damage to normal-sized characters roughly the same as before and fixes the large characters
                        modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / 10));
                    }
                    c.LastDamageSource = damageSource;
                    if (attacker == null)
                    {
                        if (damageSource is Item item)
                        {
                            attacker = item.GetComponent<Projectile>()?.User;
                            if (attacker == null)
                            {
                                attacker = item.GetComponent<MeleeWeapon>()?.User;
                            }
                        }
                    }

                    //use a position slightly from the limb's position towards the explosion
                    //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
                    Vector2 dir = worldPosition - limb.WorldPosition;
                    Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
                    AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker);
                    damages.Add(limb, attackResult.Damage);
                    
                    if (attack.StatusEffects != null && attack.StatusEffects.Any())
                    {
                        attack.SetUser(attacker);
                        var statusEffectTargets = new List<ISerializableEntity>() { c, limb };
                        foreach (StatusEffect statusEffect in attack.StatusEffects)
                        {
                            statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets);
                            statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets);
                            statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets);
                        }
                    }
                    
                    if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f))
                    {
                        Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition);
                        if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); }
                        Vector2 impulse = limbDiff * distFactor * force;
                        Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius;
                        limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f);
                    }
                }

                if (c == Character.Controlled && !c.IsDead)
                {
                    Limb head = c.AnimController.GetLimb(LimbType.Head);
                    if (damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor))
                    {
                        PlayTinnitusProjSpecific(headFactor);
                    }
                }

                //sever joints 
                if (attack.SeverLimbsProbability > 0.0f)
                {
                    foreach (Limb limb in c.AnimController.Limbs)
                    {
                        if (limb.character.Removed || limb.Removed) { continue; }
                        if (limb.IsSevered) { continue; }
                        if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; }
                        if (distFactors.TryGetValue(limb, out float distFactor))
                        {
                            if (damages.TryGetValue(limb, out float damage))
                            {
                                c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true);
                            }
                        }
                    }
                }
            }
        }
示例#16
0
        public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null)
        {
            prevExplosions.Add(new Triplet<Explosion, Vector2, float>(this, worldPosition, (float)Timing.TotalTime));
            if (prevExplosions.Count > 100)
            {
                prevExplosions.RemoveAt(0);
            }

            Hull hull = Hull.FindHull(worldPosition);
            ExplodeProjSpecific(worldPosition, hull);

            if (hull != null && !string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f)
            {
                hull.AddDecal(decal, worldPosition, decalSize, isNetworkEvent: false);
            }

            float displayRange = attack.Range;

            Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position;
            float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
            GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f);
#if CLIENT
            if (screenColor != Color.Transparent)
            {
                Color flashColor = Color.Lerp(Color.Transparent, screenColor, Math.Max((screenColorRange - cameraDist) / screenColorRange, 0.0f));
                Screen.Selected.ColorFade(flashColor, Color.Transparent, screenColorDuration);
            }
#endif

            if (displayRange < 0.1f) { return; }

            if (attack.GetStructureDamage(1.0f) > 0.0f)
            {
                RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attack.GetLevelWallDamage(1.0f), attacker);
            }

            if (BallastFloraDamage > 0.0f)
            {
                RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker);
            }

            if (EmpStrength > 0.0f)
            {
                float displayRangeSqr = displayRange * displayRange;
                foreach (Item item in Item.ItemList)
                {
                    float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
                    if (distSqr > displayRangeSqr) continue;
                    
                    float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange;

                    //damage repairable power-consuming items
                    var powered = item.GetComponent<Powered>();
                    if (powered == null || !powered.VulnerableToEMP) continue;
                    if (item.Repairables.Any())
                    {
                        item.Condition -= item.MaxCondition * EmpStrength * distFactor;
                    }

                    //discharge batteries
                    var powerContainer = item.GetComponent<PowerContainer>();
                    if (powerContainer != null)
                    {
                        powerContainer.Charge -= powerContainer.Capacity * EmpStrength * distFactor;
                    }
                }
            }

            if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(attack.Stun, 0.0f) && MathUtils.NearlyEqual(attack.GetTotalDamage(false), 0.0f))
            {
                return;
            }

            DamageCharacters(worldPosition, attack, force, damageSource, attacker);

            if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
            {
                foreach (Item item in Item.ItemList)
                {
                    if (item.Condition <= 0.0f) { continue; }
                    float dist = Vector2.Distance(item.WorldPosition, worldPosition);
                    float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
                    dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
                    if (dist > attack.Range) { continue; }

                    if (dist < attack.Range * 0.5f && applyFireEffects && !item.FireProof)
                    {
                        //don't apply OnFire effects if the item is inside a fireproof container
                        //(or if it's inside a container that's inside a fireproof container, etc)
                        Item container = item.Container;
                        bool fireProof = false;
                        while (container != null)
                        {
                            if (container.FireProof)
                            {
                                fireProof = true;
                                break;
                            }
                            container = container.Container;
                        }
                        if (!fireProof)
                        {
                            item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
                            if (item.Condition <= 0.0f && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                            {
                                GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
                            }
                        }                        
                    }

                    if (item.Prefab.DamagedByExplosions && !item.Indestructible)
                    {
                        float distFactor = 1.0f - dist / attack.Range;
                        float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;

                        Vector2 explosionPos = worldPosition;
                        if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }

                        damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition);
                        item.Condition -= damageAmount * distFactor;
                    }
                }
            }
        }