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); }