Esempio n. 1
0
        private static void SharedCallOnWeaponShot(ICharacter character, IProtoItemWeapon protoWeapon)
        {
            if (IsClient)
            {
                // start firing weapon on Client-side
                WeaponSystemClientDisplay.OnWeaponShot(character,
                                                       protoWeapon: protoWeapon,
                                                       fallbackProtoCharacter: character.ProtoCharacter,
                                                       fallbackPosition: character.Position);
            }
            else // if IsServer
            {
                using var observers = Api.Shared.GetTempList <ICharacter>();
                var eventNetworkRadius = (byte)Math.Max(
                    15,
                    Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max));

                Server.World.GetCharactersInRadius(character.TilePosition,
                                                   observers,
                                                   radius: eventNetworkRadius,
                                                   onlyPlayers: true);
                observers.Remove(character);

                Instance.CallClient(observers,
                                    _ => _.ClientRemote_OnWeaponShot(character,
                                                                     protoWeapon,
                                                                     character.ProtoCharacter,
                                                                     character.Position));
            }
        }
Esempio n. 2
0
        public WeaponReloadingState(
            ICharacter character,
            IItem item,
            IProtoItemWeapon itemProto,
            IProtoItemAmmo protoItemAmmo)
        {
            this.Item          = item;
            this.ProtoItemAmmo = protoItemAmmo;

            var reloadDuration = itemProto.AmmoReloadDuration;

            if (reloadDuration > 0)
            {
                var statName = itemProto.WeaponSkillProto?.StatNameReloadingSpeedMultiplier;
                if (statName.HasValue)
                {
                    reloadDuration *= character.SharedGetFinalStatMultiplier(statName.Value);
                }

                reloadDuration = Api.Shared.RoundDurationByServerFrameDuration(reloadDuration);
            }

            this.SecondsToReloadRemains = reloadDuration;
            //Api.Logger.WriteDev($"Weapon will be reloaded in: {this.SecondsToReloadRemains:F2} seconds");
        }
Esempio n. 3
0
 private void ClientRemote_OnWeaponHit(IProtoItemWeapon protoWeapon, WeaponHitData hitObject)
 {
     using (var tempList = Api.Shared.WrapObjectInTempList(hitObject))
     {
         WeaponSystemClientDisplay.OnWeaponHit(protoWeapon, tempList);
     }
 }
Esempio n. 4
0
        protected override void ServerOnDegradeWeapon(
            ICharacter character,
            IItem weaponItem,
            IProtoItemWeapon protoWeapon,
            List <WeaponHitData> hitObjects)
        {
            if (hitObjects.Count == 0)
            {
                // no objects were hit
                return;
            }

            var decrease = this.DurabilityDecreasePerAction;

            foreach (var hit in hitObjects)
            {
                var protoObject = hit.WorldObject.ProtoWorldObject;
                if (protoObject is IProtoObjectWall ||
                    protoObject is IProtoObjectDoor ||
                    protoObject is IProtoObjectTradingStation)
                {
                    // hit wall, door or station
                    decrease *= 5;
                    break;
                }
            }

            ItemDurabilitySystem.ServerModifyDurability(
                weaponItem,
                delta: -decrease);
        }
Esempio n. 5
0
        public static void TrySetWeapon(
            ICharacter character,
            IProtoItemWeapon protoWeapon,
            bool rebuildWeaponsCacheNow)
        {
            var privateState = character.GetPrivateState <CharacterMobPrivateState>();
            var publicState  = character.GetPublicState <CharacterMobPublicState>();
            var weaponState  = privateState.WeaponState;

            if (ReferenceEquals(weaponState.ProtoWeapon, protoWeapon))
            {
                return;
            }

            if (weaponState.CooldownSecondsRemains > 0.001 ||
                weaponState.DamageApplyDelaySecondsRemains > 0.001)
            {
                //Api.Logger.Dev("Weapon cooldown remains: " + weaponState.CooldownSecondsRemains);
                return;
            }

            weaponState.SharedSetWeaponProtoOnly(protoWeapon);
            publicState.SharedSetCurrentWeaponProtoOnly(protoWeapon);

            // can use the new selected mob weapon instantly
            weaponState.ReadySecondsRemains = weaponState.CooldownSecondsRemains = 0;

            if (!rebuildWeaponsCacheNow)
            {
                return;
            }

            WeaponSystem.SharedRebuildWeaponCache(character, weaponState);
            privateState.AttackRange = weaponState.WeaponCache.RangeMax;
        }
        public ViewModelItemTooltipCompatibleAmmoControl(IProtoItemWeapon protoItemWeapon)
        {
            var list = protoItemWeapon.CompatibleAmmoProtos.ToList();

            list.SortBy(e => e.Name);
            this.CompatibleAmmoProtos = list;
        }
Esempio n. 7
0
 public ViewModelItemTooltipCompatibleAmmoControl(IProtoItemWeapon protoItemWeapon)
 {
     this.CompatibleAmmoProtos = protoItemWeapon.CompatibleAmmoProtos
                                 .OrderBy(p => p.Name)
                                 .Select(p => p.Name)
                                 .ToList();
 }
Esempio n. 8
0
        private static WeaponFinalCache ServerCreateWeaponFinalCacheForDrone(
            ICharacter characterOwner,
            IProtoItemWeapon protoMiningTool,
            IDynamicWorldObject objectDrone)
        {
            using var tempStatsCache = TempStatsCache.GetFromPool();

            // fill only the skills cache from character (status effects have no effect)
            foreach (var pair in characterOwner.SharedGetSkills().Skills)
            {
                var protoSkill = pair.Key;
                var skillLevel = pair.Value.Level;
                protoSkill.FillEffectsCache(tempStatsCache, skillLevel);
            }

            var finalStatsCache = tempStatsCache.CalculateFinalStatsCache();

            return(new WeaponFinalCache(
                       characterOwner,
                       finalStatsCache,
                       weapon: null,
                       protoWeapon: protoMiningTool,
                       protoAmmo: null,
                       damageDescription: protoMiningTool.OverrideDamageDescription,
                       protoExplosive: null,
                       objectDrone: objectDrone));
        }
Esempio n. 9
0
 public override void ClientOnWeaponHitOrTrace(
     ICharacter firingCharacter,
     Vector2D worldPositionSource,
     IProtoItemWeapon protoWeapon,
     IProtoItemAmmo protoAmmo,
     IProtoCharacter protoCharacter,
     in Vector2Ushort fallbackCharacterPosition,
Esempio n. 10
0
        public static void OnWeaponHit(IProtoItemWeapon protoWeapon, IReadOnlyList <WeaponHitData> hitObjects)
        {
            foreach (var hitData in hitObjects)
            {
                var worldObject = hitData.WorldObject;
                if (worldObject?.ProtoGameObject == null)
                {
                    // no such object in current context
                    if (Api.IsEditor)
                    {
                        Api.Logger.Error("Unknown world object on OnWeaponHit(): " + worldObject);
                    }
                    else
                    {
                        Api.Logger.Warning("Unknown world object on OnWeaponHit(): " + worldObject);
                    }

                    continue;
                }

                var protoWorldObject    = worldObject.ProtoWorldObject;
                var objectSoundMaterial = protoWorldObject.SharedGetObjectSoundMaterial();

                var volume = SoundConstants.VolumeHit;
                // apply some volume variation
                volume *= RandomHelper.Range(0.8f, 1.0f);

                protoWeapon.SoundPresetHit.PlaySound(
                    objectSoundMaterial,
                    worldObject,
                    volume: volume,
                    pitch: RandomHelper.Range(0.95f, 1.05f));
            }
        }
Esempio n. 11
0
 public static ItemTooltipCompatibleAmmoControl Create(IProtoItemWeapon protoItemWeapon)
 {
     return(new ItemTooltipCompatibleAmmoControl()
     {
         protoItemWeapon = protoItemWeapon
     });
 }
Esempio n. 12
0
 protected override void ServerOnStaticObjectDestroyedByCharacter(
     ICharacter byCharacter,
     IProtoItemWeapon byWeaponProto,
     IStaticWorldObject targetObject)
 {
     base.ServerOnStaticObjectDestroyedByCharacter(byCharacter, byWeaponProto, targetObject);
     this.ServerOnExtractorDestroyedForDeposit(targetObject);
 }
Esempio n. 13
0
 protected override void ServerOnStaticObjectDestroyedByCharacter(
     ICharacter byCharacter,
     IProtoItemWeapon byWeaponProto,
     IStaticWorldObject targetObject)
 {
     base.ServerOnStaticObjectDestroyedByCharacter(byCharacter, byWeaponProto, targetObject);
     // it will spawn the drop items
     this.ServerOnDamageStageIncreased(byCharacter, byWeaponProto, targetObject, damageStage: DamageStagesCount);
 }
 public static ItemTooltipWeaponStats Create(
     IItem item,
     IProtoItemWeapon protoItem)
 {
     return(new ItemTooltipWeaponStats()
     {
         item = item, protoItem = protoItem
     });
 }
Esempio n. 15
0
 public DropItemContext(
     ICharacter character,
     IStaticWorldObject staticWorldObject,
     IProtoItemWeapon byWeaponProto = null)
 {
     this.ByWeaponProto     = byWeaponProto;
     this.character         = character;
     this.staticWorldObject = staticWorldObject;
 }
 protected virtual void ServerOnStaticObjectDestroyedByCharacter(
     [CanBeNull] ICharacter byCharacter,
     [CanBeNull] IProtoItemWeapon byWeaponProto,
     IStaticWorldObject targetObject)
 {
     if (byCharacter != null)
     {
         ServerStaticObjectDestroyObserver.NotifyObjectDestroyed(byCharacter, targetObject);
     }
 }
Esempio n. 17
0
        public static void OnCharacterKilled(
            ICharacter targetCharacter,
            ICharacter attackerCharacter,
            IItem weapon,
            IProtoItemWeapon protoWeapon)
        {
            // killed!
            Api.Logger.Important(
                $"Character killed: {targetCharacter} by {attackerCharacter} with {weapon?.ToString() ?? protoWeapon?.ToString()}");

            Api.SafeInvoke(
                () => CharacterKilled?.Invoke(attackerCharacter, targetCharacter));
        }
Esempio n. 18
0
        protected override void ServerOnStaticObjectDestroyedByCharacter(
            ICharacter byCharacter,
            IProtoItemWeapon byWeaponProto,
            IStaticWorldObject targetObject)
        {
            base.ServerOnStaticObjectDestroyedByCharacter(byCharacter, byWeaponProto, targetObject);

            // drop chance and gained experience depends on the vegetation growth stage
            var growthProgressFraction = this.GrowthStagesCount > 0
                                             ? GetPublicState(targetObject).GrowthStage / (double)this.GrowthStagesCount
                                             : 1;

            growthProgressFraction = MathHelper.Clamp(growthProgressFraction, 0.1, 1);

            try
            {
                var dropItemContext = new DropItemContext(byCharacter, targetObject);
                if (byWeaponProto is IProtoItemWeaponMelee)
                {
                    // a melee weapon - try drop items to character
                    var result = this.DroplistOnDestroy.TryDropToCharacter(
                        byCharacter,
                        dropItemContext,
                        probabilityMultiplier: growthProgressFraction);
                    if (result.IsEverythingCreated)
                    {
                        NotificationSystem.ServerSendItemsNotification(byCharacter, result);
                        return;
                    }

                    result.Rollback();
                }

                // not a melee weapon or cannot drop to character - drop on the ground only
                this.DroplistOnDestroy.TryDropToGround(
                    targetObject.TilePosition,
                    dropItemContext,
                    probabilityMultiplier: growthProgressFraction,
                    groundContainer: out _);
            }
            finally
            {
                if (byWeaponProto is IProtoItemToolWoodcutting)
                {
                    // add experience proportional to the tree structure points (effectively - for the time spent on woodcutting)
                    var exp = SkillWoodcutting.ExperienceAddPerStructurePoint;
                    exp *= this.StructurePointsMax * growthProgressFraction;
                    byCharacter?.ServerAddSkillExperience <SkillWoodcutting>(exp);
                }
            }
        }
Esempio n. 19
0
        private static void SetFiringAnimation(
            IProtoItemWeapon weaponProto,
            IComponentSkeleton skeletonRenderer,
            byte trackIndex,
            string fireAnimationName,
            double fireAnimationDuration,
            double fireInterval,
            bool mixWithCurrent,
            bool isLooped)
        {
            var currentFireAnimation = skeletonRenderer.GetCurrentAnimationName(trackIndex);

            if (currentFireAnimation == fireAnimationName)
            {
                // the same firing animation is already playing
                Api.Logger.Warning(
                    "Will overwrite current attack animation: "
                    + currentFireAnimation
                    + " - usually it means that the DamageApplyDelaySeconds+FireIntervalSeconds is lower than the attack animation duration for "
                    + weaponProto
                    + " (they must be matching perfectly).");
            }

            if (currentFireAnimation != null)
            {
                if (mixWithCurrent)
                {
                    skeletonRenderer.SetAnimationLoopMode(trackIndex, isLooped: false);
                    skeletonRenderer.RemoveAnimationTrackNextEntries(trackIndex);
                }
                else
                {
                    skeletonRenderer.RemoveAnimationTrack(trackIndex);
                }
            }

            // cooldown is a padding duration which makes animation "stuck" on the last frame for the specified duration
            var cooldownDuration = isLooped
                                            // in looped animation we need to match its total duration to fire interval by using cooldown
                                       ? Math.Max(0, fireInterval - fireAnimationDuration)
                                       : 0; // non-looped animation no cooldown is necessary

            skeletonRenderer.AddAnimation(
                trackIndex,
                animationName: fireAnimationName,
                isLooped: isLooped,
                customDuration: (float)fireAnimationDuration,
                cooldownDuration: (float)cooldownDuration);
        }
Esempio n. 20
0
        private void ClientExplodeAt(
            IProtoItemWeapon protoWeapon,
            Vector2D shotSourcePosition,
            Vector2D explosionWorldPosition)
        {
            var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit(protoWeapon.FireTracePreset
                                                                               ?? this.FireTracePreset,
                                                                               shotSourcePosition,
                                                                               explosionWorldPosition);

            ClientTimersSystem.AddAction(timeToHit,
                                         () => ExplosionHelper.ClientExplode(explosionWorldPosition,
                                                                             this.ExplosionPreset,
                                                                             this.ExplosionVolume));
        }
Esempio n. 21
0
        /// <summary>
        /// Returns compatible with weapon ammo group by ammo type.
        /// </summary>
        private static List <IGrouping <IProtoItemAmmo, IItem> > SharedGetCompatibleAmmoGroups(
            ICharacter character,
            IProtoItemWeapon protoWeapon)
        {
            var compatibleAmmoProtos = protoWeapon.CompatibleAmmoProtos;
            var containerInventory   = character.SharedGetPlayerContainerInventory();
            var containerHotbar      = character.SharedGetPlayerContainerHotbar();

            var allItems = containerInventory.Items.Concat(containerHotbar.Items);

            return(allItems
                   .Where(i => compatibleAmmoProtos.Contains(i.ProtoGameObject))
                   .GroupBy(a => (IProtoItemAmmo)a.ProtoItem)
                   .ToList());
        }
Esempio n. 22
0
        private static void SelectAiWeapon(
            ICharacter character,
            double distanceToTarget,
            IReadOnlyList <AiWeaponPreset> weaponList,
            out IProtoItemWeapon desiredProtoWeapon,
            out bool isWithinRange)
        {
            desiredProtoWeapon = null;
            isWithinRange      = false;

            var privateState = character.GetPrivateState <CharacterMobPrivateState>();
            var weaponState  = privateState.WeaponState;

            if (weaponState.CooldownSecondsRemains > 0.001 ||
                weaponState.DamageApplyDelaySecondsRemains > 0.001)
            {
                // cannot switch weapon now, try to use the currently selected weapon
                //Api.Logger.Dev("Weapon cooldown remains: " + weaponState.CooldownSecondsRemains
                //               + " damageApplyDelaySecondsRemains: " +  weaponState.DamageApplyDelaySecondsRemains);
                desiredProtoWeapon = weaponState.ProtoWeapon;
                foreach (var weaponPreset in weaponList)
                {
                    if (ReferenceEquals(weaponPreset.ProtoWeapon, desiredProtoWeapon))
                    {
                        isWithinRange = distanceToTarget < weaponPreset.MaxAttackRange;
                        break;
                    }
                }

                return;
            }

            // try to select the weapon from the list
            foreach (var weaponPreset in weaponList)
            {
                isWithinRange = distanceToTarget < weaponPreset.MaxAttackRange;
                if (!isWithinRange)
                {
                    continue;
                }

                desiredProtoWeapon = weaponPreset.ProtoWeapon;
                ServerMobWeaponHelper.TrySetWeapon(character,
                                                   desiredProtoWeapon,
                                                   rebuildWeaponsCacheNow: false);
                break;
            }
        }
Esempio n. 23
0
        private void ClientRemote_OnWeaponShot(
            ICharacter whoFires,
            IProtoItemWeapon protoWeapon,
            IProtoCharacter fallbackProtoCharacter,
            Vector2D fallbackPosition)
        {
            if (whoFires != null &&
                !whoFires.IsInitialized)
            {
                whoFires = null;
            }

            WeaponSystemClientDisplay.OnWeaponShot(whoFires,
                                                   protoWeapon,
                                                   fallbackProtoCharacter,
                                                   fallbackPosition);
        }
Esempio n. 24
0
        private void ServerOnDamageStageIncreased(
            [CanBeNull] ICharacter byCharacter,
            IProtoItemWeapon byWeaponProto,
            IStaticWorldObject mineralObject,
            int damageStage)
        {
            Logger.Info(
                $"{mineralObject} current damage stage changed to {damageStage}. Dropping items for that stage",
                byCharacter);

            try
            {
                var dropItemsList   = this.DropItemsConfig.GetForStage(damageStage);
                var dropItemContext = new DropItemContext(byCharacter, mineralObject, byWeaponProto);

                if (byWeaponProto is IProtoItemWeaponMelee)
                {
                    var result = dropItemsList.TryDropToCharacter(byCharacter, dropItemContext);
                    if (result.IsEverythingCreated)
                    {
                        NotificationSystem.ServerSendItemsNotification(
                            byCharacter,
                            result);
                        return;
                    }

                    result.Rollback();
                }

                // not a melee weapon or cannot drop to the character inventory - drop on the ground only
                dropItemsList.TryDropToGround(mineralObject.TilePosition,
                                              dropItemContext,
                                              out _);
            }
            finally
            {
                if (byWeaponProto is IProtoItemToolMining)
                {
                    // add experience proportional to the mineral structure points (effectively - for the time spent on mining)
                    var exp = SkillMining.ExperienceAddPerStructurePoint;
                    exp *= this.StructurePointsMax / DamageStagesCount;
                    byCharacter?.ServerAddSkillExperience <SkillMining>(exp);
                }
            }
        }
Esempio n. 25
0
 private void ClientRemote_OnWeaponHitOrTrace(
     ICharacter firingCharacter,
     IProtoItemWeapon protoWeapon,
     IProtoItemAmmo protoAmmo,
     IProtoCharacter protoCharacter,
     Vector2Ushort fallbackCharacterPosition,
     WeaponHitData[] hitObjects,
     Vector2D endPosition,
     bool endsWithHit)
 {
     WeaponSystemClientDisplay.ClientOnWeaponHitOrTrace(firingCharacter,
                                                        protoWeapon,
                                                        protoAmmo,
                                                        protoCharacter,
                                                        fallbackCharacterPosition,
                                                        hitObjects,
                                                        endPosition,
                                                        endsWithHit);
 }
Esempio n. 26
0
        /// <summary>
        /// Some weapons, such as flintlock pistol or a musket/double-barreled shotgun
        /// might have a desync issue when they're reloaded on the server
        /// right after receiving a command to stop firing.
        /// So the shots done on the client will be not done on the server.
        /// To prevent this issue, this method detects such weapons and keeps the shots flow.
        /// </summary>
        public static bool IsResetsShotsDoneNumberOnReload(IProtoItemWeapon protoWeapon)
        {
            if (protoWeapon.AmmoCapacity == 0 ||
                protoWeapon.AmmoConsumptionPerShot == 0)
            {
                return(true);
            }

            var shotsPerMagazine = protoWeapon.AmmoCapacity / protoWeapon.AmmoConsumptionPerShot;

            if (shotsPerMagazine <= 1 ||
                shotsPerMagazine <= 2 && protoWeapon.FireInterval < 0.5)
            {
                // don't reset the shots done number for this weapon
                //Logger.Dev("Don't reset - shotsPerMagazine: " + shotsPerMagazine + " - " + protoWeapon.ShortId);
                return(false);
            }

            //Logger.Dev("Reset - shotsPerMagazine: " + shotsPerMagazine + " - " + protoWeapon.ShortId);
            return(true);
        }
Esempio n. 27
0
        private static void SharedCallOnWeaponShot(
            ICharacter character,
            IProtoItemWeapon protoWeapon)
        {
            if (IsClient)
            {
                // start firing weapon on Client-side
                WeaponSystemClientDisplay.ClientOnWeaponShot(character,
                                                             partyId:
                                                             0, // not relevant here as it's the current player firing the weapon
                                                             protoWeapon: protoWeapon,
                                                             protoCharacter: character.ProtoCharacter,
                                                             fallbackPosition: character.Position.ToVector2Ushort());
            }
            else // if IsServer
            {
                using var observers = Shared.GetTempList <ICharacter>();
                var eventNetworkRadius = (byte)Math.Max(
                    15,
                    Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max));

                Server.World.GetCharactersInRadius(character.TilePosition,
                                                   observers,
                                                   radius: eventNetworkRadius,
                                                   onlyPlayers: true);
                observers.Remove(character);

                if (observers.Count > 0)
                {
                    var partyId = PartySystem.ServerGetParty(character)?.Id ?? 0;

                    Instance.CallClient(observers.AsList(),
                                        _ => _.ClientRemote_OnWeaponShot(character,
                                                                         partyId,
                                                                         protoWeapon,
                                                                         character.ProtoCharacter,
                                                                         character.Position.ToVector2Ushort()));
                }
            }
        }
Esempio n. 28
0
        protected override void ClientExplodeAt(
            IProtoItemWeapon protoWeapon,
            Vector2D shotSourcePosition,
            Vector2D explosionWorldPosition)
        {
            var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit(protoWeapon.FireTracePreset
                                                                               ?? this.FireTracePreset,
                                                                               shotSourcePosition,
                                                                               explosionWorldPosition);

            ClientTimersSystem.AddAction(
                timeToHit,
                () =>
            {
                SharedExplosionHelper.ClientExplode(explosionWorldPosition,
                                                    this.ExplosionPreset,
                                                    this.VolumeExplosion);

                // add more explosions in the X pattern as it's a fragmentation grenade
                ClientTimersSystem.AddAction(
                    0.2,
                    () =>
                {
                    SharedExplosionHelper.ClientExplode(explosionWorldPosition + (1, 1),
                                                        this.ExplosionPreset,
                                                        volume: 0);

                    SharedExplosionHelper.ClientExplode(explosionWorldPosition + (1, -1),
                                                        this.ExplosionPreset,
                                                        volume: 0);

                    SharedExplosionHelper.ClientExplode(explosionWorldPosition + (-1, 1),
                                                        this.ExplosionPreset,
                                                        volume: 0);

                    SharedExplosionHelper.ClientExplode(explosionWorldPosition + (-1, -1),
                                                        this.ExplosionPreset,
                                                        volume: 0);
                });
Esempio n. 29
0
        private static void RefreshStaticAttackAnimation(
            IComponentSkeleton skeletonRenderer,
            IProtoItemWeapon weapon,
            string firingAnimationName)
        {
            // ReSharper disable once CanExtractXamlLocalizableStringCSharp
            var staticFiringAnimationName = firingAnimationName + "_Static";
            var lastStaticAnimationName   = skeletonRenderer.GetLatestAddedAnimationName(
                AnimationTrackIndexes.ItemFiringStatic);

            if (lastStaticAnimationName == staticFiringAnimationName)
            {
                return;
            }

            if (skeletonRenderer.GetLatestAddedAnimationName(AnimationTrackIndexes.ItemFiring)
                == null)
            {
                // no current firing animation - no need the static animation
                return;
            }

            // need to update the static attack animation
            SetFiringAnimation(
                weapon,
                skeletonRenderer,
                AnimationTrackIndexes.ItemFiringStatic,
                staticFiringAnimationName,
                weapon.FireAnimationDuration,
                weapon.FireInterval,
                mixWithCurrent: true,
                isLooped: weapon.IsLoopedAttackAnimation);

            // synchronize static with non-static animation tracks
            skeletonRenderer.SetAnimationTime(
                AnimationTrackIndexes.ItemFiringStatic,
                skeletonRenderer.GetAnimationTime(AnimationTrackIndexes.ItemFiring));
        }
        public static void OnWeaponHit(IProtoItemWeapon protoWeapon, IReadOnlyList <WeaponHitData> hitObjects)
        {
            foreach (var hitData in hitObjects)
            {
                var worldObject = hitData.WorldObject;
                if (worldObject != null &&
                    !worldObject.IsInitialized)
                {
                    worldObject = null;
                }

                var protoWorldObject    = hitData.FallbackProtoWorldObject;
                var objectSoundMaterial = protoWorldObject.SharedGetObjectSoundMaterial();

                var volume = SoundConstants.VolumeHit;
                // apply some volume variation
                volume *= RandomHelper.Range(0.8f, 1.0f);
                var pitch = RandomHelper.Range(0.95f, 1.05f);

                if (worldObject != null)
                {
                    protoWeapon.SoundPresetHit.PlaySound(
                        objectSoundMaterial,
                        worldObject,
                        volume: volume,
                        pitch: pitch);
                }
                else
                {
                    protoWeapon.SoundPresetHit.PlaySound(
                        objectSoundMaterial,
                        protoWorldObject,
                        worldPosition: hitData.FallbackTilePosition.ToVector2D(),
                        volume: volume,
                        pitch: pitch);
                }
            }
        }