private void TimerTickCallback()
        {
            if (IsClient)
            {
                ClientTimersSystem.AddAction(CheckTimeIntervalSeconds, this.TimerTickCallback);
            }

            RegisteredActions.ProcessAndRemove(
                removeCondition: pair =>
            {
                if (IsServer)
                {
                    CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(pair.Character);
                }

                // remove if cannot interact
                return(!SharedIsValidInteraction(pair));
            },
                // abort action when pair removed due to the interaction check failed
                removeCallback: pair => pair.Value.FinishAction?.Invoke(isAbort: true));
        }
示例#2
0
        private static void SharedFireWeapon(
            ICharacter character,
            IItem weaponItem,
            IProtoItemWeapon protoWeapon,
            WeaponState weaponState)
        {
            protoWeapon.SharedOnFire(character, weaponState);

            var playerCharacterSkills = character.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills != null
                                       ? protoWeapon.WeaponSkillProto
                                       : null;

            if (IsServer)
            {
                protoWeaponSkill?.ServerOnShot(playerCharacterSkills); // give experience for shot
                CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(character);
            }

            var weaponCache = weaponState.WeaponCache;

            if (weaponCache == null)
            {
                // calculate new weapon cache
                RebuildWeaponCache(character, weaponState);
                weaponCache = weaponState.WeaponCache;
            }

            // raycast possible victims
            var isMeleeWeapon = protoWeapon is IProtoItemWeaponMelee;
            var fromPosition  = character.Position
                                + (0, isMeleeWeapon
                                         ? character.ProtoCharacter.CharacterWorldWeaponOffsetMelee
                                         : character.ProtoCharacter.CharacterWorldWeaponOffsetRanged);

            var toPosition = fromPosition
                             + new Vector2D(weaponCache.RangeMax, 0)
                             .RotateRad(character.ProtoCharacter.SharedGetRotationAngleRad(character));

            var collisionGroup = isMeleeWeapon
                                     ? CollisionGroups.HitboxMelee
                                     : CollisionGroups.HitboxRanged;

            using var lineTestResults = character.PhysicsBody.PhysicsSpace.TestLine(
                      fromPosition: fromPosition,
                      toPosition: toPosition,
                      collisionGroup: collisionGroup);
            var damageMultiplier    = 1d;
            var hitObjects          = new List <WeaponHitData>(isMeleeWeapon ? 1 : lineTestResults.Count);
            var characterTileHeight = character.Tile.Height;

            if (IsClient ||
                Api.IsEditor)
            {
                SharedEditorPhysicsDebugger.SharedVisualizeTestResults(lineTestResults, collisionGroup);
            }

            foreach (var testResult in lineTestResults)
            {
                var testResultPhysicsBody = testResult.PhysicsBody;
                var attackedProtoTile     = testResultPhysicsBody.AssociatedProtoTile;
                if (attackedProtoTile != null)
                {
                    if (attackedProtoTile.Kind != TileKind.Solid)
                    {
                        // non-solid obstacle - skip
                        continue;
                    }

                    var attackedTile = IsServer
                                           ? Server.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position)
                                           : Client.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position);

                    if (attackedTile.Height < characterTileHeight)
                    {
                        // attacked tile is below - ignore it
                        continue;
                    }

                    // tile on the way - blocking damage ray
                    break;
                }

                var damagedObject = testResultPhysicsBody.AssociatedWorldObject;
                if (damagedObject == character)
                {
                    // ignore collision with self
                    continue;
                }

                if (!(damagedObject.ProtoGameObject is IDamageableProtoWorldObject damageableProto))
                {
                    // shoot through this object
                    continue;
                }

                var damagedCharacter = damagedObject as ICharacter;
                if (!ReferenceEquals(damagedCharacter, null))
                {
                    // don't allow damage is there is no direct line of sight on physical colliders layer between the two objects
                    if (SharedHasTileObstacle(character, characterTileHeight, damagedCharacter))
                    {
                        continue;
                    }
                }
                else if (damagedObject is IStaticWorldObject staticWorldObject &&
                         characterTileHeight != staticWorldObject.OccupiedTile.Height)
                {
                    // don't allow damage to static objects on a different height level
                    continue;
                }

                if (!damageableProto.SharedOnDamage(
                        weaponCache,
                        damagedObject,
                        damageMultiplier,
                        out var obstacleBlockDamageCoef,
                        out var damageApplied))
                {
                    // not hit
                    continue;
                }

                if (IsServer)
                {
                    weaponCache.ProtoWeapon
                    .ServerOnDamageApplied(weaponCache.Weapon, character, damagedObject, damageApplied);

                    if (damageApplied > 0 &&
                        !ReferenceEquals(damagedCharacter, null))
                    {
                        CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(damagedCharacter);
                    }

                    if (damageApplied > 0 &&
                        protoWeaponSkill != null)
                    {
                        // give experience for damage
                        protoWeaponSkill.ServerOnDamageApplied(playerCharacterSkills, damagedObject, damageApplied);

                        if (!ReferenceEquals(damagedCharacter, null) &&
                            (damagedCharacter.GetPublicState <ICharacterPublicState>()
                             .CurrentStats
                             .HealthCurrent
                             <= 0))
                        {
                            // give weapon experience for kill
                            Logger.Info("Killed " + damagedCharacter, character);
                            protoWeaponSkill.ServerOnKill(playerCharacterSkills, killedCharacter: damagedCharacter);

                            if (damagedCharacter.ProtoCharacter is ProtoCharacterMob protoMob)
                            {
                                // give hunting skill experience for mob kill
                                var experience = SkillHunting.ExperienceForKill;
                                experience *= protoMob.MobKillExperienceMultiplier;
                                if (experience > 0)
                                {
                                    playerCharacterSkills.ServerAddSkillExperience <SkillHunting>(experience);
                                }
                            }
                        }
                    }
                }

                if (obstacleBlockDamageCoef < 0 ||
                    obstacleBlockDamageCoef > 1)
                {
                    Logger.Error(
                        "Obstacle block damage coefficient should be >= 0 and <= 1 - wrong calculation by "
                        + damageableProto);
                    break;
                }

                //var hitPosition = testResultPhysicsBody.Position + testResult.Penetration;
                hitObjects.Add(new WeaponHitData(damagedObject)); //, hitPosition));

                if (isMeleeWeapon)
                {
                    // currently melee weapon could attack only one object on the ray
                    break;
                }

                damageMultiplier = damageMultiplier * (1.0 - obstacleBlockDamageCoef);
                if (damageMultiplier <= 0)
                {
                    // target blocked the damage ray
                    break;
                }
            }

            if (hitObjects.Count > 0)
            {
                if (IsClient)
                {
                    // display weapon shot on Client-side
                    WeaponSystemClientDisplay.OnWeaponHit(protoWeapon, hitObjects);
                }
                else // if server
                {
                    // display damages on clients in scope of every damaged object
                    using var scopedBy = Api.Shared.GetTempList <ICharacter>();
                    foreach (var hitObject in hitObjects)
                    {
                        if (hitObject.WorldObject.IsDestroyed)
                        {
                            continue;
                        }

                        Server.World.GetScopedByPlayers(hitObject.WorldObject, scopedBy);
                        // don't notify the attacking character
                        scopedBy.Remove(character);

                        if (hitObject.WorldObject is ICharacter damagedCharacter)
                        {
                            // notify the damaged character
                            scopedBy.Add(damagedCharacter);
                        }

                        if (scopedBy.Count == 0)
                        {
                            continue;
                        }

                        Instance.CallClient(scopedBy,
                                            _ => _.ClientRemote_OnWeaponHit(protoWeapon, hitObject));
                        scopedBy.Clear();
                    }
                }
            }

            if (IsServer)
            {
                protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, hitObjects);
            }
        }
示例#3
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 != 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 &&
                            damagedObject is ICharacter damagedCharacter)
                        {
                            CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(damagedCharacter);
                        }

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

                        weaponFinalCache.ProtoExplosive?.ServerOnObjectHitByExplosion(damagedObject,
                                                                                      damageApplied,
                                                                                      weaponFinalCache);
                    }
                }
            }
        }
示例#4
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 collisionGroup = null)
        {
            var playerCharacterSkills = weaponFinalCache.Character?.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills != null
                                       ? weaponFinalCache.ProtoWeapon?.WeaponSkillProto
                                       : null;

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

            if (collisionGroup is null)
            {
                collisionGroup = CollisionGroups.Default;
            }

            var defaultCollisionGroup = collisionGroup;

            CollectDamagedPhysicalObjects(defaultCollisionGroup);
            CollectDamagedPhysicalObjects(CollisionGroups.HitboxMelee);
            CollectDamagedPhysicalObjects(CollisionGroups.HitboxRanged);

            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, collisionGroup));

            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)
                {
                    CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(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));
            }
        }
示例#5
0
        private static void SharedShotWeaponHitscan(
            ICharacter character,
            IProtoItemWeapon protoWeapon,
            Vector2D fromPosition,
            WeaponFinalCache weaponCache,
            Vector2D?customTargetPosition,
            IProtoCharacterCore characterProtoCharacter,
            double fireSpreadAngleOffsetDeg,
            CollisionGroup collisionGroup,
            bool isMeleeWeapon,
            IDynamicWorldObject characterCurrentVehicle,
            ProtoSkillWeapons protoWeaponSkill,
            PlayerCharacterSkills playerCharacterSkills,
            ITempList <IWorldObject> allHitObjects)
        {
            Vector2D toPosition;
            var      rangeMax = weaponCache.RangeMax;

            if (customTargetPosition.HasValue)
            {
                var direction = customTargetPosition.Value - fromPosition;
                // ensure the max range is not exceeded
                direction  = direction.ClampMagnitude(rangeMax);
                toPosition = fromPosition + direction;
            }
            else
            {
                toPosition = fromPosition
                             + new Vector2D(rangeMax, 0)
                             .RotateRad(characterProtoCharacter.SharedGetRotationAngleRad(character)
                                        + fireSpreadAngleOffsetDeg * Math.PI / 180.0);
            }

            using var lineTestResults = character.PhysicsBody.PhysicsSpace.TestLine(
                      fromPosition: fromPosition,
                      toPosition: toPosition,
                      collisionGroup: collisionGroup);
            var damageMultiplier    = 1d;
            var hitObjects          = new List <WeaponHitData>(isMeleeWeapon ? 1 : lineTestResults.Count);
            var characterTileHeight = character.Tile.Height;

            if (IsClient ||
                Api.IsEditor)
            {
                SharedEditorPhysicsDebugger.SharedVisualizeTestResults(lineTestResults, collisionGroup);
            }

            var isDamageRayStopped = false;

            foreach (var testResult in lineTestResults.AsList())
            {
                var testResultPhysicsBody = testResult.PhysicsBody;
                var attackedProtoTile     = testResultPhysicsBody.AssociatedProtoTile;
                if (attackedProtoTile != null)
                {
                    if (attackedProtoTile.Kind != TileKind.Solid)
                    {
                        // non-solid obstacle - skip
                        continue;
                    }

                    var attackedTile = IsServer
                                           ? Server.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position)
                                           : Client.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position);

                    if (attackedTile.Height < characterTileHeight)
                    {
                        // attacked tile is below - ignore it
                        continue;
                    }

                    // tile on the way - blocking damage ray
                    isDamageRayStopped = true;
                    var hitData = new WeaponHitData(testResult.PhysicsBody.Position
                                                    + SharedOffsetHitWorldPositionCloserToTileHitboxCenter(
                                                        testResultPhysicsBody,
                                                        testResult.Penetration,
                                                        isRangedWeapon: !isMeleeWeapon));
                    hitObjects.Add(hitData);

                    weaponCache.ProtoWeapon
                    .SharedOnHit(weaponCache,
                                 null,
                                 0,
                                 hitData,
                                 out _);
                    break;
                }

                var damagedObject = testResultPhysicsBody.AssociatedWorldObject;
                if (ReferenceEquals(damagedObject, character) ||
                    ReferenceEquals(damagedObject, characterCurrentVehicle))
                {
                    // ignore collision with self
                    continue;
                }

                if (!(damagedObject.ProtoGameObject is IDamageableProtoWorldObject damageableProto))
                {
                    // shoot through this object
                    continue;
                }

                // don't allow damage is there is no direct line of sight on physical colliders layer between the two objects
                if (SharedHasTileObstacle(character.Position,
                                          characterTileHeight,
                                          damagedObject,
                                          targetPosition: testResult.PhysicsBody.Position
                                          + testResult.PhysicsBody.CenterOffset))
                {
                    continue;
                }

                using (CharacterDamageContext.Create(attackerCharacter: character,
                                                     damagedObject as ICharacter,
                                                     protoWeaponSkill))
                {
                    if (!damageableProto.SharedOnDamage(
                            weaponCache,
                            damagedObject,
                            damageMultiplier,
                            damagePostMultiplier: 1.0,
                            out var obstacleBlockDamageCoef,
                            out var damageApplied))
                    {
                        // not hit
                        continue;
                    }

                    var hitData = new WeaponHitData(damagedObject,
                                                    testResult.Penetration.ToVector2F());
                    weaponCache.ProtoWeapon
                    .SharedOnHit(weaponCache,
                                 damagedObject,
                                 damageApplied,
                                 hitData,
                                 out var isDamageStop);

                    if (isDamageStop)
                    {
                        obstacleBlockDamageCoef = 1;
                    }

                    if (IsServer)
                    {
                        if (damageApplied > 0 &&
                            damagedObject is ICharacter damagedCharacter)
                        {
                            CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(damagedCharacter);
                        }

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

                    if (obstacleBlockDamageCoef < 0 ||
                        obstacleBlockDamageCoef > 1)
                    {
                        Logger.Error(
                            "Obstacle block damage coefficient should be >= 0 and <= 1 - wrong calculation by "
                            + damageableProto);
                        break;
                    }

                    hitObjects.Add(hitData);

                    if (isMeleeWeapon)
                    {
                        // currently melee weapon could attack only one object on the ray
                        isDamageRayStopped = true;
                        break;
                    }

                    damageMultiplier *= 1.0 - obstacleBlockDamageCoef;
                    if (damageMultiplier <= 0)
                    {
                        // target blocked the damage ray
                        isDamageRayStopped = true;
                        break;
                    }
                }
            }

            var shotEndPosition = GetShotEndPosition(isDamageRayStopped,
                                                     hitObjects,
                                                     toPosition,
                                                     isRangedWeapon: !isMeleeWeapon);

            if (hitObjects.Count == 0)
            {
                protoWeapon.SharedOnMiss(weaponCache,
                                         shotEndPosition);
            }

            SharedCallOnWeaponHitOrTrace(character,
                                         protoWeapon,
                                         weaponCache.ProtoAmmo,
                                         shotEndPosition,
                                         hitObjects,
                                         endsWithHit: isDamageRayStopped);

            foreach (var entry in hitObjects)
            {
                if (!entry.IsCliffsHit &&
                    !allHitObjects.Contains(entry.WorldObject))
                {
                    allHitObjects.Add(entry.WorldObject);
                }
            }
        }
示例#6
0
        private static void SharedFireWeapon(
            ICharacter character,
            IItem weaponItem,
            IProtoItemWeapon protoWeapon,
            WeaponState weaponState)
        {
            if (!protoWeapon.SharedOnFire(character, weaponState))
            {
                return;
            }

            var playerCharacterSkills = character.SharedGetSkills();
            var protoWeaponSkill      = playerCharacterSkills != null
                                       ? protoWeapon.WeaponSkillProto
                                       : null;

            if (IsServer)
            {
                protoWeaponSkill?.ServerOnShot(playerCharacterSkills); // give experience for shot
                CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(character);
            }

            var weaponCache = weaponState.WeaponCache;

            if (weaponCache is null)
            {
                SharedRebuildWeaponCache(character, weaponState);
                weaponCache = weaponState.WeaponCache;
            }

            var characterCurrentVehicle = character.IsNpc
                                              ? null
                                              : character.SharedGetCurrentVehicle();

            var isMeleeWeapon            = protoWeapon is IProtoItemWeaponMelee;
            var characterProtoCharacter  = (IProtoCharacterCore)character.ProtoCharacter;
            var fromPosition             = characterProtoCharacter.SharedGetWeaponFireWorldPosition(character, isMeleeWeapon);
            var fireSpreadAngleOffsetDeg = protoWeapon.SharedUpdateAndGetFirePatternCurrentSpreadAngleDeg(weaponState);

            var collisionGroup = protoWeapon.CollisionGroup;

            using var allHitObjects = Shared.GetTempList <IWorldObject>();
            var shotsPerFire = weaponCache.FireScatterPreset.ProjectileAngleOffets;

            foreach (var angleOffsetDeg in shotsPerFire)
            {
                SharedShotWeaponHitscan(character,
                                        protoWeapon,
                                        fromPosition,
                                        weaponCache,
                                        weaponState.CustomTargetPosition,
                                        characterProtoCharacter,
                                        fireSpreadAngleOffsetDeg + angleOffsetDeg,
                                        collisionGroup,
                                        isMeleeWeapon,
                                        characterCurrentVehicle,
                                        protoWeaponSkill,
                                        playerCharacterSkills,
                                        allHitObjects);
            }

            if (IsServer)
            {
                protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, allHitObjects.AsList());
            }
        }