public static void CreateItems(List <PurchasedItem> itemsToSpawn)
        {
            if (itemsToSpawn.Count == 0)
            {
                return;
            }

            WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);

            if (wp == null)
            {
                DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
                return;
            }

            Hull cargoRoom = Hull.FindHull(wp.WorldPosition);

            if (cargoRoom == null)
            {
                DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
                return;
            }

#if CLIENT
            new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true));
#endif

            Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>();
            ItemPrefab containerPrefab = null;
            foreach (PurchasedItem pi in itemsToSpawn)
            {
                Vector2 position = new Vector2(
                    Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20),
                    cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.ItemPrefab.Size.Y / 2);

                ItemContainer itemContainer = null;
                if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
                {
                    itemContainer = availableContainers.Keys.ToList().Find(ac =>
                                                                           ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
                                                                           ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()));

                    if (itemContainer == null)
                    {
                        containerPrefab = MapEntityPrefab.List.Find(ep =>
                                                                    ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
                                                                    (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))) as ItemPrefab;

                        if (containerPrefab == null)
                        {
                            DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!");
                            continue;
                        }

                        Item containerItem = new Item(containerPrefab, position, wp.Submarine);
                        itemContainer = containerItem.GetComponent <ItemContainer>();
                        if (itemContainer == null)
                        {
                            DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!");
                            continue;
                        }
                        availableContainers.Add(itemContainer, itemContainer.Capacity);
#if SERVER
                        if (GameMain.Server != null)
                        {
                            Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
                        }
#endif
                    }
                }
                for (int i = 0; i < pi.Quantity; i++)
                {
                    if (itemContainer == null)
                    {
                        //no container, place at the waypoint
#if SERVER
                        if (GameMain.Server != null)
                        {
                            Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine);
                        }
                        else
                        {
#endif
                        new Item(pi.ItemPrefab, position, wp.Submarine);
#if SERVER
                    }
#endif
                        continue;
                    }
                    //if the intial container has been removed due to it running out of space, add a new container
                    //of the same type and begin filling it
                    if (!availableContainers.ContainsKey(itemContainer))
                    {
                        Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine);
                        itemContainer = containerItemOverFlow.GetComponent <ItemContainer>();
                        availableContainers.Add(itemContainer, itemContainer.Capacity);
#if SERVER
                        if (GameMain.Server != null)
                        {
                            Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
                        }
#endif
                    }

                    //place in the container
#if SERVER
                    if (GameMain.Server != null)
                    {
                        Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory);
                    }
                    else
                    {
#endif
                    var item = new Item(pi.ItemPrefab, position, wp.Submarine);
                    itemContainer.Inventory.TryPutItem(item, null);
#if SERVER
                }
#endif

                    //reduce the number of available slots in the container
                    //if there is a container
                    if (availableContainers.ContainsKey(itemContainer))
                    {
                        availableContainers[itemContainer]--;
                    }
                    if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0)
                    {
                        availableContainers.Remove(itemContainer);
                    }
                }
            }
            itemsToSpawn.Clear();
        }
Example #2
0
        public static void CreateItems(List <PurchasedItem> itemsToSpawn)
        {
            if (itemsToSpawn.Count == 0)
            {
                return;
            }

            WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);

            if (wp == null)
            {
                DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
                return;
            }

            Hull cargoRoom = Hull.FindHull(wp.WorldPosition);

            if (cargoRoom == null)
            {
                DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
                return;
            }

#if CLIENT
            new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true), new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon");
#else
            foreach (Client client in GameMain.Server.ConnectedClients)
            {
                ChatMessage msg = ChatMessage.Create("", $"CargoSpawnNotification~[roomname]=ยง{cargoRoom.RoomName}", ChatMessageType.ServerMessageBoxInGame, null);
                msg.IconStyle = "StoreShoppingCrateIcon";
                GameMain.Server.SendDirectChatMessage(msg, client);
            }
#endif

            Dictionary <ItemContainer, int> availableContainers = new Dictionary <ItemContainer, int>();
            ItemPrefab containerPrefab = null;
            foreach (PurchasedItem pi in itemsToSpawn)
            {
                float floorPos = cargoRoom.Rect.Y - cargoRoom.Rect.Height;

                Vector2 position = new Vector2(
                    Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20),
                    floorPos);

                //check where the actual floor structure is in case the bottom of the hull extends below it
                if (Submarine.PickBody(
                        ConvertUnits.ToSimUnits(new Vector2(position.X, cargoRoom.Rect.Y - cargoRoom.Rect.Height / 2)),
                        ConvertUnits.ToSimUnits(position),
                        collisionCategory: Physics.CollisionWall) != null)
                {
                    float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y);
                    if (floorStructurePos > floorPos)
                    {
                        floorPos = floorStructurePos;
                    }
                }
                position.Y = floorPos + pi.ItemPrefab.Size.Y / 2;

                ItemContainer itemContainer = null;
                if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
                {
                    itemContainer = availableContainers.Keys.ToList().Find(ac =>
                                                                           ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
                                                                           ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()));

                    if (itemContainer == null)
                    {
                        containerPrefab = ItemPrefab.Prefabs.Find(ep =>
                                                                  ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
                                                                  (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));

                        if (containerPrefab == null)
                        {
                            DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!");
                            continue;
                        }

                        Item containerItem = new Item(containerPrefab, position, wp.Submarine);
                        itemContainer = containerItem.GetComponent <ItemContainer>();
                        if (itemContainer == null)
                        {
                            DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!");
                            continue;
                        }
                        availableContainers.Add(itemContainer, itemContainer.Capacity);
#if SERVER
                        if (GameMain.Server != null)
                        {
                            Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
                        }
#endif
                    }
                }
                for (int i = 0; i < pi.Quantity; i++)
                {
                    if (itemContainer == null)
                    {
                        //no container, place at the waypoint
                        if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                        {
                            Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine, onSpawned: itemSpawned);
                        }
                        else
                        {
                            var item = new Item(pi.ItemPrefab, position, wp.Submarine);
                            itemSpawned(item);
                        }
                        continue;
                    }
                    //if the intial container has been removed due to it running out of space, add a new container
                    //of the same type and begin filling it
                    if (!availableContainers.ContainsKey(itemContainer))
                    {
                        Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine);
                        itemContainer = containerItemOverFlow.GetComponent <ItemContainer>();
                        availableContainers.Add(itemContainer, itemContainer.Capacity);
#if SERVER
                        if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                        {
                            Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
                        }
#endif
                    }

                    //place in the container
                    if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                    {
                        Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory, onSpawned: itemSpawned);
                    }
                    else
                    {
                        var item = new Item(pi.ItemPrefab, position, wp.Submarine);
                        itemContainer.Inventory.TryPutItem(item, null);
                        itemSpawned(item);
                    }
Example #3
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)
                    {
                        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
                    var obstacles = Submarine.PickBodies(limb.SimPosition, explosionPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
                    foreach (var body in obstacles)
                    {
                        if (body.UserData is Item item)
                        {
                            var door = item.GetComponent <Door>();
                            if (door != null && !door.IsBroken)
                            {
                                distFactor *= 0.01f;
                            }
                        }
                        else if (body.UserData is Structure structure)
                        {
                            int sectionIndex = structure.FindSectionIndex(worldPosition, world: true, clamp: true);
                            if (structure.SectionBodyDisabled(sectionIndex))
                            {
                                continue;
                            }
                            else if (structure.SectionIsLeaking(sectionIndex))
                            {
                                distFactor *= 0.1f;
                            }
                            else
                            {
                                distFactor *= 0.01f;
                            }
                        }
                        else
                        {
                            distFactor *= 0.1f;
                        }
                    }
                    if (distFactor <= 0.05f)
                    {
                        continue;
                    }

                    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);
                            }
                        }
                    }
                }
            }
        }
Example #4
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), 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;
                    }
                    if (Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.5f)
                    {
                        continue;
                    }
                    if (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 limbRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
                        float dist       = Vector2.Distance(item.WorldPosition, worldPosition);
                        dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
                        if (dist > attack.Range)
                        {
                            continue;
                        }
                        float distFactor   = 1.0f - dist / attack.Range;
                        float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
                        item.Condition -= damageAmount * distFactor;
                    }
                }
            }
        }
Example #5
0
        partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull)
        {
            if (shockwave)
            {
                GameMain.ParticleManager.CreateParticle("shockwave", worldPosition,
                                                        Vector2.Zero, 0.0f, hull);
            }

            hull ??= Hull.FindHull(worldPosition, useWorldCoordinates: true);
            bool underwater = hull == null || worldPosition.Y < hull.WorldSurface;

            if (underwater && underwaterBubble)
            {
                var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition, Vector2.Zero, 0.0f, hull);
                if (underwaterExplosion != null)
                {
                    underwaterExplosion.Size      *= MathHelper.Clamp(attack.Range / 150.0f, 0.5f, 10.0f);
                    underwaterExplosion.StartDelay = 0.0f;
                }
            }

            for (int i = 0; i < attack.Range * 0.1f; i++)
            {
                if (!underwater)
                {
                    float particleSpeed = Rand.Range(0.0f, 1.0f);
                    particleSpeed = particleSpeed * particleSpeed * attack.Range;

                    if (flames)
                    {
                        float particleScale = MathHelper.Clamp(attack.Range * 0.0025f, 0.5f, 2.0f);
                        var   flameParticle = GameMain.ParticleManager.CreateParticle("explosionfire",
                                                                                      ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull),
                                                                                      Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
                        if (flameParticle != null)
                        {
                            flameParticle.Size *= particleScale;
                        }
                    }
                    if (smoke)
                    {
                        GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke",
                                                                ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull),
                                                                Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
                    }
                }
                else if (underwaterBubble)
                {
                    Vector2 bubblePos = Rand.Vector(Rand.Range(0.0f, attack.Range * 0.5f));

                    GameMain.ParticleManager.CreateParticle("risingbubbles", worldPosition + bubblePos,
                                                            Vector2.Zero, 0.0f, hull);

                    if (i < attack.Range * 0.02f)
                    {
                        var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition + bubblePos,
                                                                                          Vector2.Zero, 0.0f, hull);
                        if (underwaterExplosion != null)
                        {
                            underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 300.0f, 0.5f, 2.0f) * Rand.Range(0.8f, 1.2f);
                        }
                    }
                }

                if (sparks)
                {
                    GameMain.ParticleManager.CreateParticle("spark", worldPosition,
                                                            Rand.Vector(Rand.Range(500.0f, 800.0f)), 0.0f, hull);
                }
            }

            if (flash)
            {
                float displayRange = flashRange.HasValue ? flashRange.Value : attack.Range;
                if (displayRange < 0.1f)
                {
                    return;
                }
                var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null);
                CoroutineManager.StartCoroutine(DimLight(light));
            }
        }