Пример #1
0
        public static bool SharedHasObstaclesInTheWay(
            Vector2D fromPosition,
            IPhysicsSpace physicsSpace,
            IWorldObject worldObject,
            bool sendDebugEvents)
        {
            var worldObjectCenter = SharedGetWorldObjectCenter(worldObject);
            var worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside(
                fromPosition,
                CollisionGroups.Default,
                out var isSuccess);

            if (!isSuccess)
            {
                // the physics body seems to not have the default collider, let's check for the click area instead
                worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside(
                    fromPosition,
                    CollisionGroups.ClickArea,
                    out _);
            }

            return(SharedHasObstaclesInTheWay(fromPosition,
                                              physicsSpace,
                                              worldObject,
                                              worldObjectCenter,
                                              worldObjectPointClosestToCharacter,
                                              sendDebugEvents,
                                              CollisionGroups.Default));
Пример #2
0
        protected virtual void ServerExecuteVehicleExplosion(
            Vector2D positionEpicenter,
            IPhysicsSpace physicsSpace,
            WeaponFinalCache weaponFinalCache)
        {
            WeaponExplosionSystem.ServerProcessExplosionCircle(
                positionEpicenter: positionEpicenter,
                physicsSpace: physicsSpace,
                damageDistanceMax: this.DestroyedVehicleDamageRadius,
                weaponFinalCache: weaponFinalCache,
                damageOnlyDynamicObjects: false,
                isDamageThroughObstacles: false,
                callbackCalculateDamageCoefByDistanceForStaticObjects: CalcDamageCoefByDistance,
                callbackCalculateDamageCoefByDistanceForDynamicObjects: CalcDamageCoefByDistance);

            double CalcDamageCoefByDistance(double distance)
            {
                var distanceThreshold = 0.5;

                if (distance <= distanceThreshold)
                {
                    return(1);
                }

                distance -= distanceThreshold;
                distance  = Math.Max(0, distance);

                var maxDistance = this.DestroyedVehicleDamageRadius;

                maxDistance -= distanceThreshold;
                maxDistance  = Math.Max(0, maxDistance);

                return(1 - Math.Min(distance / maxDistance, 1));
            }
        }
Пример #3
0
        public static bool SharedHasObstaclesOnTheWay(
            ICharacter character,
            Vector2D characterCenter,
            IPhysicsSpace physicsSpace,
            IWorldObject worldObject,
            bool sendDebugEvents)
        {
            var worldObjectCenter = worldObject.TilePosition.ToVector2D() + worldObject.PhysicsBody.CenterOffset;
            var worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside(
                characterCenter,
                CollisionGroups.Default,
                out var isSuccess);

            if (!isSuccess)
            {
                // the physics body seems to not have the default collider, let's check for the click area instead
                worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside(
                    characterCenter,
                    CollisionGroups.ClickArea,
                    out _);
            }

            return(SharedHasObstaclesOnTheWay(character,
                                              characterCenter,
                                              physicsSpace,
                                              worldObject,
                                              worldObjectCenter,
                                              worldObjectPointClosestToCharacter,
                                              sendDebugEvents));
        }
Пример #4
0
        public static void ServerExplode(
            [CanBeNull] ICharacter character,
            [CanBeNull] IProtoObjectExplosive protoObjectExplosive,
            ExplosionPreset explosionPreset,
            Vector2D epicenterPosition,
            DamageDescription damageDescriptionCharacters,
            IPhysicsSpace physicsSpace,
            ExecuteExplosionDelegate executeExplosionCallback)
        {
            ValidateIsServer();

            // schedule explosion charred ground spawning
            ServerTimersSystem.AddAction(
                // the delay is quite small and just needed to ensure
                // that the charred ground spawned some time after this object is destroyed
                delaySeconds: explosionPreset.SpriteAnimationDuration * 0.5,
                () =>
            {
                var tilePosition = (Vector2Ushort)epicenterPosition;
                if (!Server.World.GetTile(tilePosition)
                    .StaticObjects
                    .Any(so => so.ProtoStaticWorldObject is ObjectCharredGround))
                {
                    // spawn charred ground
                    var objectCharredGround =
                        Server.World.CreateStaticWorldObject <ObjectCharredGround>(tilePosition);
                    var objectCharredGroundOffset = epicenterPosition - tilePosition.ToVector2D();
                    if (objectCharredGroundOffset != Vector2D.Zero)
                    {
                        ObjectCharredGround.ServerSetWorldOffset(objectCharredGround,
                                                                 objectCharredGroundOffset.ToVector2F());
                    }
                }
            });

            // schedule explosion damage
            ServerTimersSystem.AddAction(
                delaySeconds: explosionPreset.ServerDamageApplyDelay,
                () =>
            {
                // prepare weapon caches
                var characterFinalStatsCache = character != null
                                                       ? character.SharedGetFinalStatsCache()
                                                       : FinalStatsCache.Empty;

                var weaponFinalCache = new WeaponFinalCache(character,
                                                            characterFinalStatsCache,
                                                            weapon: null,
                                                            protoWeapon: null,
                                                            protoObjectExplosive: protoObjectExplosive,
                                                            damageDescription: damageDescriptionCharacters);

                // execute explosion
                executeExplosionCallback(
                    positionEpicenter: epicenterPosition,
                    physicsSpace: physicsSpace,
                    weaponFinalCache: weaponFinalCache);
            });
        }
 protected virtual void ServerExecuteExplosion(
     Vector2D positionEpicenter,
     IPhysicsSpace physicsSpace,
     WeaponFinalCache weaponFinalCache)
 {
     WeaponExplosionSystem.ServerProcessExplosionCircle(
         positionEpicenter: positionEpicenter,
         physicsSpace: physicsSpace,
         damageDistanceMax: this.DamageRadius,
         weaponFinalCache: weaponFinalCache,
         damageOnlyDynamicObjects: false,
         callbackCalculateDamageCoefByDistance: this.ServerCalculateDamageCoefByDistance);
 }
Пример #6
0
        // Ensure that player can hit objects only on the same height level
        // and can fire through over the pits (the cliffs of the lower heights).
        public static bool SharedHasTileObstacle(
            Vector2D fromPosition,
            byte characterTileHeight,
            Vector2D targetPosition,
            IPhysicsSpace physicsSpace,
            bool anyCliffIsAnObstacle)
        {
            using var testResults = physicsSpace.TestLine(
                      fromPosition,
                      targetPosition,
                      collisionGroup: CollisionGroups.Default,
                      sendDebugEvent: false);

            foreach (var testResult in testResults.AsList())
            {
                var testResultPhysicsBody = testResult.PhysicsBody;
                var protoTile             = testResultPhysicsBody.AssociatedProtoTile;

                if (protoTile is null ||
                    protoTile.Kind != TileKind.Solid)
                {
                    continue;
                }

                var testResultPosition = testResultPhysicsBody.Position;
                var attackedTile       = IsServer
                                       ? Server.World.GetTile((Vector2Ushort)testResultPosition)
                                       : Client.World.GetTile((Vector2Ushort)testResultPosition);
                if (attackedTile.IsSlope)
                {
                    // slope is not an obstacle
                    continue;
                }

                // found collision with a cliff
                if (anyCliffIsAnObstacle)
                {
                    return(true);
                }

                if (attackedTile.Height >= characterTileHeight)
                {
                    return(true); // cliff to higher tile is always an obstacle
                }
            }

            return(false); // no obstacles
        }
Пример #7
0
 public static bool SharedHasObstaclesOnTheWay(
     ICharacter character,
     Vector2D characterCenter,
     IPhysicsSpace physicsSpace,
     Vector2D worldObjectCenter,
     Vector2D worldObjectPointClosestToCharacter,
     bool sendDebugEvents)
 {
     return(SharedHasObstaclesOnTheWay(character,
                                       characterCenter,
                                       physicsSpace,
                                       worldObject: null,
                                       worldObjectCenter,
                                       worldObjectPointClosestToCharacter,
                                       sendDebugEvents));
 }
Пример #8
0
 protected override void ServerExecuteExplosion(
     Vector2D positionEpicenter,
     IPhysicsSpace physicsSpace,
     WeaponFinalCache weaponFinalCache)
 {
     WeaponExplosionSystem.ServerProcessExplosionBomberman(
         positionEpicenter: positionEpicenter,
         physicsSpace: physicsSpace,
         damageDistanceFullDamage: DamageRadiusFullDamage,
         damageDistanceMax: DamageRadiusMax,
         damageDistanceDynamicObjectsOnly: DamageRadiusDynamicObjectsOnly,
         weaponFinalCache: weaponFinalCache,
         callbackCalculateDamageCoefByDistanceForStaticObjects:
         this.ServerCalculateDamageCoefByDistance,
         callbackCalculateDamageCoefByDistanceForDynamicObjects:
         ServerCalculateDamageCoefByDistanceForDynamicObjects);
 }
Пример #9
0
 public override void ServerExecuteExplosion(
     Vector2D positionEpicenter,
     IPhysicsSpace physicsSpace,
     WeaponFinalCache weaponFinalCache)
 {
     WeaponExplosionSystem.ServerProcessExplosionCircle(
         positionEpicenter: positionEpicenter,
         physicsSpace: physicsSpace,
         damageDistanceMax: this.DamageRadius,
         weaponFinalCache: weaponFinalCache,
         damageOnlyDynamicObjects: true,
         isDamageThroughObstacles: this.IsDamageThroughObstacles,
         callbackCalculateDamageCoefByDistanceForStaticObjects:
         this.ServerCalculateDamageCoefByDistanceForStaticObjects,
         callbackCalculateDamageCoefByDistanceForDynamicObjects: this
         .ServerCalculateDamageCoefByDistanceForDynamicObjects,
         // Missiles are falling from the sky and the explosion circles are clearly designated.
         // Players expecting that they will be not damaged when they stand outside the circles.
         collisionGroups: new[] { CollisionGroup.Default });
 }
Пример #10
0
        protected static bool ServerCheckAnyTileCollisions(
            IPhysicsSpace physicsSpace,
            Vector2D centerLocation,
            double radius)
        {
            foreach (var result in physicsSpace.TestCircle(
                         centerLocation,
                         radius,
                         collisionGroup: CollisionGroups.Default,
                         sendDebugEvent: false).EnumerateAndDispose())
            {
                if (result.PhysicsBody.AssociatedProtoTile != null)
                {
                    // collision with a tile found - probably a cliff or water
                    // avoid spawning there
                    return(false);
                }
            }

            return(true);
        }
Пример #11
0
        public override void ServerExecuteExplosion(
            Vector2D positionEpicenter,
            IPhysicsSpace physicsSpace,
            WeaponFinalCache weaponFinalCache)
        {
            WeaponExplosionSystem.ServerProcessExplosionCircle(
                positionEpicenter: positionEpicenter,
                physicsSpace: physicsSpace,
                damageDistanceMax: this.DamageRadius,
                weaponFinalCache: weaponFinalCache,
                damageOnlyDynamicObjects: true,
                isDamageThroughObstacles: this.IsDamageThroughObstacles,
                callbackCalculateDamageCoefByDistanceForStaticObjects:
                this.ServerCalculateDamageCoefByDistanceForStaticObjects,
                callbackCalculateDamageCoefByDistanceForDynamicObjects: this
                .ServerCalculateDamageCoefByDistanceForDynamicObjects,
                // Missiles are falling from the sky and the explosion circles are clearly designated.
                // Players are expecting that they will be not damaged when they stand outside the circles.
                collisionGroups: new[] { CollisionGroups.Default });

            // this is only for hoverboards as hoverboards on the ground have no physical collider
            WeaponExplosionSystem.ServerProcessExplosionCircle(
                positionEpicenter: positionEpicenter,
                physicsSpace: physicsSpace,
                damageDistanceMax: this.DamageRadius,
                weaponFinalCache: weaponFinalCache,
                damageOnlyDynamicObjects: true,
                isDamageThroughObstacles: this.IsDamageThroughObstacles,
                callbackCalculateDamageCoefByDistanceForStaticObjects:
                this.ServerCalculateDamageCoefByDistanceForStaticObjects,
                callbackCalculateDamageCoefByDistanceForDynamicObjects: this
                .ServerCalculateDamageCoefByDistanceForDynamicObjects,
                // this is only for hoverboards
                collisionGroups: new[] { CollisionGroups.HitboxMelee },
                // can damage only hoverboards
                filterCanDamage: worldObject => worldObject.ProtoGameObject is IProtoVehicleHoverboard);
        }
        public static void ServerProcessExplosionCircle(
            Vector2D positionEpicenter,
            IPhysicsSpace physicsSpace,
            double damageDistanceMax,
            WeaponFinalCache weaponFinalCache,
            bool damageOnlyDynamicObjects,
            Func <double, double> callbackCalculateDamageCoefByDistance)
        {
            var protoObjectExplosive = weaponFinalCache.ProtoObjectExplosive;

            Api.Assert(protoObjectExplosive != null,
                       "Weapon final cache should contain the exploded object");

            var damageCandidates = new HashSet <IWorldObject>();

            // collect all damaged physics objects
            var collisionGroup = CollisionGroups.HitboxRanged;

            using (var testResults = physicsSpace.TestCircle(positionEpicenter,
                                                             radius: damageDistanceMax,
                                                             collisionGroup: collisionGroup))
            {
                foreach (var testResult in testResults)
                {
                    var testResultPhysicsBody = testResult.PhysicsBody;
                    var damagedObject         = testResultPhysicsBody.AssociatedWorldObject;

                    if (damageOnlyDynamicObjects &&
                        damagedObject is IStaticWorldObject)
                    {
                        continue;
                    }

                    if (!(damagedObject?.ProtoWorldObject is IDamageableProtoWorldObject))
                    {
                        // non-damageable world object
                        continue;
                    }

                    damageCandidates.Add(damagedObject);
                }

                if (!damageOnlyDynamicObjects)
                {
                    // Collect all the damageable static objects in the explosion radius
                    // which don't have a collider colliding with the HitboxRanged collision group.
                    var startTilePosition        = positionEpicenter.ToVector2Ushort();
                    var damageDistanceMaxRounded = (int)damageDistanceMax;
                    var damageDistanceMaxSqr     = damageDistanceMax * damageDistanceMax;
                    var minTileX = startTilePosition.X - damageDistanceMaxRounded;
                    var minTileY = startTilePosition.Y - damageDistanceMaxRounded;
                    var maxTileX = startTilePosition.X + damageDistanceMaxRounded;
                    var maxTileY = startTilePosition.Y + damageDistanceMaxRounded;

                    for (var x = minTileX; x <= maxTileX; x++)
                    {
                        for (var y = minTileY; y <= maxTileY; y++)
                        {
                            if (x < 0 ||
                                x > ushort.MaxValue ||
                                y < 0 ||
                                y > ushort.MaxValue)
                            {
                                continue;
                            }

                            if (new Vector2Ushort((ushort)x, (ushort)y)
                                .TileSqrDistanceTo(startTilePosition)
                                > damageDistanceMaxSqr)
                            {
                                // too far
                                continue;
                            }

                            var tileObjects = Api.Server.World.GetStaticObjects(new Vector2Ushort((ushort)x, (ushort)y));
                            if (tileObjects.Count == 0)
                            {
                                continue;
                            }

                            foreach (var tileObject in tileObjects)
                            {
                                if (!(tileObject.ProtoStaticWorldObject is IDamageableProtoWorldObject))
                                {
                                    // non-damageable
                                    continue;
                                }

                                if (tileObject.PhysicsBody.HasAnyShapeCollidingWithGroup(collisionGroup))
                                {
                                    // has a collider colliding with the HitboxRanged collision group so we ignore this
                                    continue;
                                }

                                damageCandidates.Add(tileObject);
                            }
                        }
                    }
                }

                // order by distance to explosion center
                var orderedDamagedObjects =
                    damageCandidates.OrderBy(ServerExplosionGetDistanceToEpicenter(positionEpicenter));
                // process all damaged objects
                foreach (var damagedObject in orderedDamagedObjects)
                {
                    if (ServerHasObstacleForExplosion(physicsSpace, positionEpicenter, damagedObject))
                    {
                        continue;
                    }

                    var distanceToDamagedObject = ServerCalculateDistanceToDamagedObject(positionEpicenter,
                                                                                         damagedObject);
                    var damageMultiplier = callbackCalculateDamageCoefByDistance(distanceToDamagedObject);
                    damageMultiplier = MathHelper.Clamp(damageMultiplier, 0, 1);

                    var damageableProto = (IDamageableProtoWorldObject)damagedObject.ProtoGameObject;
                    damageableProto.SharedOnDamage(
                        weaponFinalCache,
                        damagedObject,
                        damageMultiplier,
                        out _,
                        out _);
                }
            }
        }
Пример #13
0
        public static void ServerExplode(
            [CanBeNull] ICharacter character,
            [CanBeNull] IProtoObjectExplosive protoObjectExplosive,
            ExplosionPreset explosionPreset,
            Vector2D epicenterPosition,
            DamageDescription damageDescriptionCharacters,
            IPhysicsSpace physicsSpace,
            ExecuteExplosionDelegate executeExplosionCallback)
        {
            ValidateIsServer();

            // schedule explosion charred ground spawning
            ServerTimersSystem.AddAction(
                delaySeconds: explosionPreset.SpriteAnimationDuration * 0.5,
                () =>
            {
                var tilePosition = (Vector2Ushort)epicenterPosition;

                // remove existing charred ground objects at the same tile
                foreach (var staticWorldObject in Shared.WrapInTempList(
                             Server.World.GetTile(tilePosition).StaticObjects))
                {
                    if (staticWorldObject.ProtoStaticWorldObject is ObjectCharredGround)
                    {
                        Server.World.DestroyObject(staticWorldObject);
                    }
                }

                // spawn charred ground
                var objectCharredGround = Server.World
                                          .CreateStaticWorldObject <ObjectCharredGround>(tilePosition);
                var objectCharredGroundOffset = epicenterPosition - tilePosition.ToVector2D();
                if (objectCharredGroundOffset != Vector2D.Zero)
                {
                    ObjectCharredGround.ServerSetWorldOffset(objectCharredGround,
                                                             (Vector2F)objectCharredGroundOffset);
                }
            });

            // schedule explosion damage
            ServerTimersSystem.AddAction(
                delaySeconds: explosionPreset.ServerDamageApplyDelay,
                () =>
            {
                // prepare weapon caches
                var characterFinalStatsCache = character?.SharedGetFinalStatsCache()
                                               ?? FinalStatsCache.Empty;

                var weaponFinalCache = new WeaponFinalCache(character,
                                                            characterFinalStatsCache,
                                                            weapon: null,
                                                            protoWeapon: null,
                                                            protoObjectExplosive: protoObjectExplosive,
                                                            damageDescription: damageDescriptionCharacters);

                // execute explosion
                executeExplosionCallback(
                    positionEpicenter: epicenterPosition,
                    physicsSpace: physicsSpace,
                    weaponFinalCache: weaponFinalCache);
            });
        }
Пример #14
0
        public static void ServerProcessExplosionCircle(
            Vector2D positionEpicenter,
            IPhysicsSpace physicsSpace,
            double damageDistanceMax,
            WeaponFinalCache weaponFinalCache,
            bool damageOnlyDynamicObjects,
            bool isDamageThroughObstacles,
            Func <double, double> callbackCalculateDamageCoefByDistanceForStaticObjects,
            Func <double, double> callbackCalculateDamageCoefByDistanceForDynamicObjects,
            [CanBeNull] CollisionGroup[] collisionGroups = null)
        {
            var playerCharacterSkills = weaponFinalCache.Character?.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills is not null
                                       ? weaponFinalCache.ProtoWeapon?.WeaponSkillProto
                                       : null;

            // collect all damaged objects via physics space
            var damageCandidates = new HashSet <IWorldObject>();

            collisionGroups ??= new[]
            {
                CollisionGroup.Default,
                CollisionGroups.HitboxMelee,
                CollisionGroups.HitboxRanged
            };

            var defaultCollisionGroup = collisionGroups[0];

            foreach (var collisionGroup in collisionGroups)
            {
                CollectDamagedPhysicalObjects(collisionGroup);
            }

            void CollectDamagedPhysicalObjects(CollisionGroup collisionGroup)
            {
                using var testResults = physicsSpace.TestCircle(positionEpicenter,
                                                                radius: damageDistanceMax,
                                                                collisionGroup: collisionGroup);
                foreach (var testResult in testResults.AsList())
                {
                    var testResultPhysicsBody = testResult.PhysicsBody;
                    var damagedObject         = testResultPhysicsBody.AssociatedWorldObject;

                    if (damageOnlyDynamicObjects &&
                        damagedObject is IStaticWorldObject)
                    {
                        continue;
                    }

                    if (!(damagedObject?.ProtoWorldObject is IDamageableProtoWorldObject))
                    {
                        // non-damageable world object
                        continue;
                    }

                    damageCandidates.Add(damagedObject);
                }
            }

            if (!damageOnlyDynamicObjects)
            {
                // Collect all the damageable static objects in the explosion radius
                // which don't have a collider colliding with the collision group.
                var startTilePosition        = positionEpicenter.ToVector2Ushort();
                var damageDistanceMaxRounded = (int)damageDistanceMax;
                var damageDistanceMaxSqr     = damageDistanceMax * damageDistanceMax;
                var minTileX = startTilePosition.X - damageDistanceMaxRounded;
                var minTileY = startTilePosition.Y - damageDistanceMaxRounded;
                var maxTileX = startTilePosition.X + damageDistanceMaxRounded;
                var maxTileY = startTilePosition.Y + damageDistanceMaxRounded;

                for (var x = minTileX; x <= maxTileX; x++)
                {
                    for (var y = minTileY; y <= maxTileY; y++)
                    {
                        if (x < 0 ||
                            x > ushort.MaxValue ||
                            y < 0 ||
                            y > ushort.MaxValue)
                        {
                            continue;
                        }

                        if (new Vector2Ushort((ushort)x, (ushort)y)
                            .TileSqrDistanceTo(startTilePosition)
                            > damageDistanceMaxSqr)
                        {
                            // too far
                            continue;
                        }

                        var tileObjects = Api.Server.World.GetStaticObjects(new Vector2Ushort((ushort)x, (ushort)y));
                        if (tileObjects.Count == 0)
                        {
                            continue;
                        }

                        foreach (var tileObject in tileObjects)
                        {
                            if (!(tileObject.ProtoStaticWorldObject is IDamageableProtoWorldObject))
                            {
                                // non-damageable
                                continue;
                            }

                            if (tileObject.PhysicsBody.HasAnyShapeCollidingWithGroup(defaultCollisionGroup))
                            {
                                // has a collider colliding with the collision group so we ignore this
                                continue;
                            }

                            damageCandidates.Add(tileObject);
                        }
                    }
                }
            }

            // order by distance to explosion center
            var orderedDamageCandidates = damageCandidates.OrderBy(
                ServerExplosionGetDistanceToEpicenter(positionEpicenter, defaultCollisionGroup));

            var hitCharacters = new List <WeaponHitData>();

            // process all damage candidates
            foreach (var damagedObject in orderedDamageCandidates)
            {
                if (!isDamageThroughObstacles &&
                    ServerHasObstacleForExplosion(physicsSpace,
                                                  positionEpicenter,
                                                  damagedObject,
                                                  defaultCollisionGroup))
                {
                    continue;
                }

                var distanceToDamagedObject = ServerCalculateDistanceToDamagedObject(positionEpicenter,
                                                                                     damagedObject);
                var damagePreMultiplier =
                    damagedObject is IDynamicWorldObject
                        ? callbackCalculateDamageCoefByDistanceForDynamicObjects(distanceToDamagedObject)
                        : callbackCalculateDamageCoefByDistanceForStaticObjects(distanceToDamagedObject);

                damagePreMultiplier = MathHelper.Clamp(damagePreMultiplier, 0, 1);

                var damageableProto = (IDamageableProtoWorldObject)damagedObject.ProtoGameObject;
                damageableProto.SharedOnDamage(
                    weaponFinalCache,
                    damagedObject,
                    damagePreMultiplier,
                    damagePostMultiplier: 1.0,
                    out _,
                    out var damageApplied);

                if (damageApplied > 0 &&
                    damagedObject is ICharacter damagedCharacter)
                {
                    hitCharacters.Add(new WeaponHitData(damagedCharacter,
                                                        (0,
                                                         damagedCharacter
                                                         .ProtoCharacter.CharacterWorldWeaponOffsetRanged)));
                }

                if (damageApplied > 0)
                {
                    // give experience for damage
                    protoWeaponSkill?.ServerOnDamageApplied(playerCharacterSkills,
                                                            damagedObject,
                                                            damageApplied);
                }

                weaponFinalCache.ProtoExplosive?.ServerOnObjectHitByExplosion(damagedObject,
                                                                              damageApplied,
                                                                              weaponFinalCache);

                (weaponFinalCache.ProtoWeapon as ProtoItemMobWeaponNova)?
                .ServerOnObjectHitByNova(damagedObject,
                                         damageApplied,
                                         weaponFinalCache);
            }

            if (hitCharacters.Count == 0)
            {
                return;
            }

            // display damages on clients in scope of every damaged object
            var observers = new HashSet <ICharacter>();

            using var tempList = Api.Shared.GetTempList <ICharacter>();

            foreach (var hitObject in hitCharacters)
            {
                if (hitObject.WorldObject is ICharacter damagedCharacter &&
                    !damagedCharacter.IsNpc)
                {
                    // notify the damaged character
                    observers.Add(damagedCharacter);
                }

                Server.World.GetScopedByPlayers(hitObject.WorldObject, tempList);
                tempList.Clear();
                observers.AddRange(tempList.AsList());
            }

            // add all observers within the sound radius
            var eventNetworkRadius = (byte)Math.Max(
                20,
                Math.Ceiling(1.5 * damageDistanceMax));

            tempList.Clear();
            Server.World.GetCharactersInRadius(positionEpicenter.ToVector2Ushort(),
                                               tempList,
                                               radius: eventNetworkRadius,
                                               onlyPlayers: true);
            observers.AddRange(tempList.AsList());

            if (observers.Count > 0)
            {
                Instance.CallClient(observers,
                                    _ => _.ClientRemote_OnCharactersHitByExplosion(hitCharacters));
            }
        }
Пример #15
0
        public static void ServerExplode(
            [CanBeNull] ICharacter character,
            [CanBeNull] IProtoExplosive protoExplosive,
            [CanBeNull] IProtoItemWeapon protoWeapon,
            ExplosionPreset explosionPreset,
            Vector2D epicenterPosition,
            DamageDescription damageDescriptionCharacters,
            IPhysicsSpace physicsSpace,
            ExecuteExplosionDelegate executeExplosionCallback)
        {
            ValidateIsServer();

            // schedule explosion charred ground spawning
            var protoObjectCharredGround = explosionPreset.ProtoObjectCharredGround;

            if (protoObjectCharredGround is not null)
            {
                ServerTimersSystem.AddAction(
                    delaySeconds: explosionPreset.SpriteAnimationDuration * 0.5,
                    () =>
                {
                    var tilePosition          = (Vector2Ushort)(epicenterPosition - protoObjectCharredGround.Layout.Center);
                    var canSpawnCharredGround = true;

                    var tile = Server.World.GetTile(tilePosition);
                    if (tile.ProtoTile.Kind != TileKind.Solid ||
                        tile.EightNeighborTiles.Any(t => t.ProtoTile.Kind != TileKind.Solid))
                    {
                        // allow charred ground only on solid ground
                        canSpawnCharredGround = false;
                    }

                    if (canSpawnCharredGround)
                    {
                        // remove existing charred ground objects at the same tile
                        foreach (var staticWorldObject in Shared.WrapInTempList(
                                     tile.StaticObjects)
                                 .EnumerateAndDispose())
                        {
                            switch (staticWorldObject.ProtoStaticWorldObject)
                            {
                            case ProtoObjectCharredGround _:
                                Server.World.DestroyObject(staticWorldObject);
                                break;

                            case IProtoObjectDeposit _:
                                // don't show charred ground over resource deposits (it looks wrong)
                                canSpawnCharredGround = false;
                                break;
                            }
                        }
                    }

                    if (canSpawnCharredGround &&
                        PveSystem.ServerIsPvE)
                    {
                        var bounds = protoObjectCharredGround.Layout.Bounds;
                        if (LandClaimSystem.SharedIsLandClaimedByAnyone(
                                new RectangleInt(tilePosition, bounds.Size + (1, 1))))
                        {
                            // ensure that it's not possible to create charred ground in a land claim area in PvE
                            canSpawnCharredGround = false;
                        }
                    }

                    if (canSpawnCharredGround)
                    {
                        // spawn charred ground
                        var objectCharredGround =
                            Server.World.CreateStaticWorldObject(protoObjectCharredGround,
                                                                 tilePosition);
                        var objectCharredGroundOffset = epicenterPosition - tilePosition.ToVector2D();
                        if (objectCharredGroundOffset != Vector2D.Zero)
                        {
                            ProtoObjectCharredGround.ServerSetWorldOffset(objectCharredGround,
                                                                          (Vector2F)objectCharredGroundOffset);
                        }
                    }
                });
            }

            // schedule explosion damage
            ServerTimersSystem.AddAction(
                delaySeconds: explosionPreset.ServerDamageApplyDelay,
                () =>
            {
                // prepare weapon caches
                var characterFinalStatsCache = character?.SharedGetFinalStatsCache()
                                               ?? FinalStatsCache.Empty;

                var weaponFinalCache = new WeaponFinalCache(character,
                                                            characterFinalStatsCache,
                                                            weapon: null,
                                                            protoWeapon: protoWeapon,
                                                            protoAmmo: null,
                                                            damageDescription: damageDescriptionCharacters,
                                                            protoExplosive: protoExplosive);

                // execute explosion
                executeExplosionCallback(
                    positionEpicenter: epicenterPosition,
                    physicsSpace: physicsSpace,
                    weaponFinalCache: weaponFinalCache);
            });
        }
        /// <summary>
        /// Bomberman-style explosion penetrating the walls in a cross.
        /// </summary>
        public static void ServerProcessExplosionBomberman(
            Vector2D positionEpicenter,
            IPhysicsSpace physicsSpace,
            int damageDistanceFullDamage,
            int damageDistanceMax,
            double damageDistanceDynamicObjectsOnly,
            WeaponFinalCache weaponFinalCache,
            Func <double, double> callbackCalculateDamageCoefByDistanceForStaticObjects,
            Func <double, double> callbackCalculateDamageCoefByDistanceForDynamicObjects)
        {
            var protoObjectExplosive = weaponFinalCache.ProtoObjectExplosive;

            Api.Assert(protoObjectExplosive != null,
                       "Weapon final cache should contain the exploded object");

            Api.Assert(damageDistanceMax >= damageDistanceFullDamage,
                       $"{nameof(damageDistanceMax)} must be >= {nameof(damageDistanceFullDamage)}");

            var world          = Api.Server.World;
            var damagedObjects = new HashSet <IWorldObject>();

            ProcessExplosionDirection(-1, 0); // left
            ProcessExplosionDirection(0, 1);  // top
            ProcessExplosionDirection(1, 0);  // right
            ProcessExplosionDirection(0, -1); // bottom

            ServerProcessExplosionCircle(positionEpicenter,
                                         physicsSpace,
                                         damageDistanceDynamicObjectsOnly,
                                         weaponFinalCache,
                                         damageOnlyDynamicObjects: true,
                                         callbackCalculateDamageCoefByDistance:
                                         callbackCalculateDamageCoefByDistanceForDynamicObjects);

            void ProcessExplosionDirection(int xOffset, int yOffset)
            {
                var fromPosition = positionEpicenter.ToVector2Ushort();

                for (var offsetIndex = 1; offsetIndex <= damageDistanceMax; offsetIndex++)
                {
                    var tile = world.GetTile(fromPosition.X + offsetIndex * xOffset,
                                             fromPosition.Y + offsetIndex * yOffset,
                                             logOutOfBounds: false);

                    if (!tile.IsValidTile ||
                        tile.IsCliff)
                    {
                        return;
                    }

                    var tileStaticObjects            = tile.StaticObjects;
                    IStaticWorldObject damagedObject = null;
                    foreach (var staticWorldObject in tileStaticObjects)
                    {
                        if (staticWorldObject.ProtoGameObject is IProtoObjectWall ||
                            staticWorldObject.ProtoGameObject is IProtoObjectDoor)
                        {
                            // damage only walls and doors
                            damagedObject = staticWorldObject;
                            break;
                        }
                    }

                    if (damagedObject == null)
                    {
                        // no wall or door there
                        if (offsetIndex > damageDistanceFullDamage)
                        {
                            // stop damage propagation
                            return;
                        }

                        continue;
                    }

                    if (!damagedObjects.Add(damagedObject))
                    {
                        // the object is already damaged
                        // (from another direction which might be theoretically possible in some future cases)
                        continue;
                    }

                    var distanceToDamagedObject = offsetIndex;
                    var damageMultiplier        =
                        callbackCalculateDamageCoefByDistanceForStaticObjects(distanceToDamagedObject);
                    damageMultiplier = MathHelper.Clamp(damageMultiplier, 0, 1);

                    var damageableProto = (IDamageableProtoWorldObject)damagedObject.ProtoGameObject;
                    damageableProto.SharedOnDamage(
                        weaponFinalCache,
                        damagedObject,
                        damageMultiplier,
                        out _,
                        out _);
                }
            }
        }
        private static bool ServerHasObstacleForExplosion(
            IPhysicsSpace physicsSpace,
            Vector2D positionEpicenter,
            IWorldObject targetWorldObject)
        {
            //// doesn't work as expected due to the penetration resolution not implemented yet
            //// for the line segment collision with other shapes
            //// get closest point to the explosion epicenter
            ////var targetPosition = targetWorldObject.PhysicsBody.ClampPointInside(
            ////    positionEpicenter,
            ////    CollisionGroups.Default);
            //
            //var targetPosition = targetWorldObject.PhysicsBody.Position
            //                     + targetWorldObject.PhysicsBody.CenterOffset;

            if (targetWorldObject.PhysicsBody == null)
            {
                return(false);
            }

            var targetPosition = ServerGetClosestPointToExplosionEpicenter(targetWorldObject.PhysicsBody,
                                                                           positionEpicenter);

            using (var obstaclesOnTheWay = physicsSpace.TestLine(
                       positionEpicenter,
                       targetPosition,
                       collisionGroup: CollisionGroups.HitboxRanged))
            {
                //obstaclesOnTheWay.SortBy(
                //    ServerExplosionGetDistanceToEpicenter(positionEpicenter));

                foreach (var testResult in obstaclesOnTheWay)
                {
                    var testPhysicsBody = testResult.PhysicsBody;
                    if (testPhysicsBody.AssociatedProtoTile != null)
                    {
                        // obstacle tile on the way
                        return(true);
                    }

                    var testWorldObject = testPhysicsBody.AssociatedWorldObject;
                    if (testWorldObject == targetWorldObject)
                    {
                        // not an obstacle - it's the target object itself
                        // stop checking collisions as we've reached the target object
                        return(false);
                    }

                    if (testWorldObject is ICharacter)
                    {
                        // not an obstacle - character is not considered as an obstacle for the explosion
                        continue;
                    }

                    if (testWorldObject.ProtoWorldObject is IDamageableProtoWorldObject damageableProtoWorldObject &&
                        damageableProtoWorldObject.ObstacleBlockDamageCoef < 1)
                    {
                        // damage goes through
                        continue;
                    }

                    // obstacle object on the way
                    return(true);
                }

                return(false);
            }
        }
Пример #18
0
        /// <summary>
        /// Bomberman-style explosion penetrating the walls in a cross.
        /// </summary>
        public static void ServerProcessExplosionBomberman(
            Vector2D positionEpicenter,
            IPhysicsSpace physicsSpace,
            int damageDistanceFullDamage,
            int damageDistanceMax,
            double damageDistanceDynamicObjectsOnly,
            WeaponFinalCache weaponFinalCache,
            Func <double, double> callbackCalculateDamageCoefByDistanceForStaticObjects,
            Func <double, double> callbackCalculateDamageCoefByDistanceForDynamicObjects)
        {
            Api.Assert(damageDistanceMax >= damageDistanceFullDamage,
                       $"{nameof(damageDistanceMax)} must be >= {nameof(damageDistanceFullDamage)}");

            var playerCharacterSkills = weaponFinalCache.Character?.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills is not null
                                       ? weaponFinalCache.ProtoWeapon?.WeaponSkillProto
                                       : null;

            var world             = Api.Server.World;
            var allDamagedObjects = new HashSet <IWorldObject>();

            ProcessExplosionDirection(-1, 0);  // left
            ProcessExplosionDirection(0, 1);   // top
            ProcessExplosionDirection(1, 0);   // right
            ProcessExplosionDirection(0, -1);  // bottom

            ServerProcessExplosionCircle(positionEpicenter,
                                         physicsSpace,
                                         damageDistanceDynamicObjectsOnly,
                                         weaponFinalCache,
                                         damageOnlyDynamicObjects: true,
                                         isDamageThroughObstacles: false,
                                         callbackCalculateDamageCoefByDistanceForStaticObjects:
                                         callbackCalculateDamageCoefByDistanceForStaticObjects,
                                         callbackCalculateDamageCoefByDistanceForDynamicObjects:
                                         callbackCalculateDamageCoefByDistanceForDynamicObjects);

            void ProcessExplosionDirection(int xOffset, int yOffset)
            {
                foreach (var(damagedObject, offsetIndex) in
                         SharedEnumerateExplosionBombermanDirectionTilesWithTargets(positionEpicenter,
                                                                                    damageDistanceFullDamage,
                                                                                    damageDistanceMax,
                                                                                    world,
                                                                                    xOffset,
                                                                                    yOffset))
                {
                    if (damagedObject is null)
                    {
                        continue;
                    }

                    if (!allDamagedObjects.Add(damagedObject))
                    {
                        // the object is already damaged
                        // (from another direction which might be theoretically possible in some future cases)
                        continue;
                    }

                    var distanceToDamagedObject = offsetIndex;
                    // this explosion pattern selects only the static objects as targets
                    var damagePreMultiplier = callbackCalculateDamageCoefByDistanceForStaticObjects(
                        distanceToDamagedObject);
                    damagePreMultiplier = MathHelper.Clamp(damagePreMultiplier, 0, 1);

                    var damageableProto = (IDamageableProtoWorldObject)damagedObject.ProtoGameObject;
                    damageableProto.SharedOnDamage(
                        weaponFinalCache,
                        damagedObject,
                        damagePreMultiplier,
                        damagePostMultiplier: 1.0,
                        out _,
                        out var damageApplied);

                    if (Api.IsServer)
                    {
                        if (damageApplied > 0)
                        {
                            // give experience for damage
                            protoWeaponSkill?.ServerOnDamageApplied(playerCharacterSkills,
                                                                    damagedObject,
                                                                    damageApplied);
                        }

                        weaponFinalCache.ProtoExplosive?.ServerOnObjectHitByExplosion(damagedObject,
                                                                                      damageApplied,
                                                                                      weaponFinalCache);
                    }
                }
            }
        }
Пример #19
0
        private static bool ServerIsCanSpawn(
            SpawnRequest spawnRequest,
            ObjectSpawnPreset preset,
            IReadOnlyDictionary <Vector2Ushort, SpawnZoneArea> spawnZoneAreas,
            Vector2Ushort spawnPosition,
            IPhysicsSpace physicsSpace,
            out SpawnZoneArea resultSpawnArea,
            out bool isSectorDensityExceeded)
        {
            if (ServerWorldService.GetTile(spawnPosition)
                .IsCliffOrSlope)
            {
                // quick discard - don't spawn on cliff or slope
                resultSpawnArea         = null;
                isSectorDensityExceeded = false;
                return(false);
            }

            var presetPadding             = preset.Padding;
            var presetCustomObjectPadding = preset.CustomObjectPadding;

            resultSpawnArea = null;
            var resultSpawnAreaStartPosition = SpawnZoneArea.CalculateStartPosition(spawnPosition);

            foreach (var area in EnumerateAdjacentZoneAreas(resultSpawnAreaStartPosition, spawnZoneAreas))
            {
                foreach (var nearbyPreset in area.WorldObjectsByPreset)
                {
                    var    nearbyObjectPreset = nearbyPreset.Key;
                    double padding;

                    if (nearbyObjectPreset != null)
                    {
                        // preset found
                        if (presetCustomObjectPadding.TryGetValue(nearbyObjectPreset, out padding))
                        {
                            // use custom padding value
                        }
                        else
                        {
                            // don't have custom padding
                            padding = Math.Max(presetPadding, nearbyObjectPreset.Padding);
                        }
                    }
                    else
                    {
                        // preset of another object not defined in this spawn list - use default object spawn padding
                        padding = DefaultObjectSpawnPadding;
                    }

                    foreach (var nearbyObjectTilePosition in nearbyPreset.Value)
                    {
                        var distance = spawnPosition.TileSqrDistanceTo(nearbyObjectTilePosition);

                        // Actually using < will be more correct, but it will produce not so nice-looking result
                        // (objects could touch each other on each side).
                        // So we insist objects must don't even touch each other on their
                        // left/up/right/down edges (but the diagonal corners touch is ok).
                        if (distance <= padding * padding)
                        {
                            // too close
                            isSectorDensityExceeded = false;
                            return(false);
                        }
                    }
                }

                if (resultSpawnAreaStartPosition == area.StartPosition)
                {
                    resultSpawnArea = area;
                }
            }

            var needToCheckLandClaimPresence = true;

            if (preset.IsContainsOnlyStaticObjects)
            {
                needToCheckLandClaimPresence = !ServerWorldService.GetTile(spawnPosition)
                                               .ProtoTile
                                               .IsRestrictingConstruction;
            }

            if (needToCheckLandClaimPresence &&
                ServerCheckLandClaimAreaPresence(spawnPosition, preset.PaddingToLandClaimAreas))
            {
                // the land is claimed by players
                resultSpawnArea         = null;
                isSectorDensityExceeded = false;
                return(false);
            }

            if (preset.CustomCanSpawnCheckCallback != null &&
                !preset.CustomCanSpawnCheckCallback(physicsSpace, spawnPosition))
            {
                // custom spawn check failed
                resultSpawnArea         = null;
                isSectorDensityExceeded = false;
                return(false);
            }

            if (resultSpawnArea == null)
            {
                // no area exist (will be created)
                isSectorDensityExceeded = false;
                return(true);
            }

            if (!spawnRequest.UseSectorDensity)
            {
                isSectorDensityExceeded = false;
                return(true);
            }

            // ensure that the area/sector density is not exceeded for this spawn preset
            isSectorDensityExceeded = IsAreaLocalDensityExceeded(spawnRequest,
                                                                 preset,
                                                                 resultSpawnArea,
                                                                 out var countToSpawnRemains);
            if (isSectorDensityExceeded)
            {
                // already spawned too many objects of the required type in the area
                return(false);
            }

            if (countToSpawnRemains < 1)
            {
                // density allows to spawn an extra object of this type with some small probability
                if (!RandomHelper.RollWithProbability(countToSpawnRemains))
                {
                    return(false);
                }
            }

            return(true);
        }
Пример #20
0
        public static bool SharedHasObstaclesInTheWay(
            Vector2D fromPosition,
            IPhysicsSpace physicsSpace,
            [CanBeNull] IWorldObject worldObject,
            Vector2D worldObjectCenter,
            Vector2D worldObjectPointClosestToCharacter,
            bool sendDebugEvents,
            CollisionGroup collisionGroup)
        {
            // let's test by casting rays from "fromPosition" (usually it's the character's center) to:
            // 0) world object center
            // 1) world object point closest to the character
            // 2) combined - take X from center, take Y from closest
            // 3) combined - take X from closest, take Y from center
            if (TestHasObstacle(worldObjectCenter) &&
                TestHasObstacle(worldObjectPointClosestToCharacter) &&
                TestHasObstacle((worldObjectCenter.X,
                                 worldObjectPointClosestToCharacter.Y)) &&
                TestHasObstacle((worldObjectPointClosestToCharacter.X, worldObjectCenter.Y)))
            {
                // has obstacle
                return(true);
            }

            return(false);

            // local method for testing if there is an obstacle from current to the specified position
            bool TestHasObstacle(Vector2D toPosition)
            {
                using var obstaclesInTheWay = physicsSpace.TestLine(
                          fromPosition,
                          toPosition,
                          collisionGroup,
                          sendDebugEvent: sendDebugEvents);
                foreach (var test in obstaclesInTheWay.AsList())
                {
                    var testPhysicsBody = test.PhysicsBody;
                    if (testPhysicsBody.AssociatedProtoTile is not null)
                    {
                        // obstacle tile on the way
                        return(true);
                    }

                    var testWorldObject = testPhysicsBody.AssociatedWorldObject;
                    if (ReferenceEquals(testWorldObject, worldObject))
                    {
                        // not an obstacle - it's the target object itself
                        // stop checking collisions as we've reached the target object
                        return(false);
                    }

                    if (testWorldObject is IDynamicWorldObject)
                    {
                        // dynamic world objects are not assumed as an obstacle
                        continue;
                    }

                    // no need for this check anymore as we're checking for general "is ICharacter" above
                    //if (ReferenceEquals(testWorldObject, character))
                    //{
                    //    // not an obstacle - it's the player's character itself
                    //    continue;
                    //}

                    if (!testWorldObject.ProtoWorldObject
                        .SharedIsAllowedObjectToInteractThrough(testWorldObject))
                    {
                        // obstacle object on the way
                        return(true);
                    }
                }

                // no obstacles
                return(false);
            }
        }