void AddFadingAnimations()
        {
            foreach (int chrID in Mod.Characters.Keys)
            {
                if (chrID == 0)
                {
                    continue;
                }

                TAE           enemyTAE = Mod[chrID].GetTAE(chrID);
                TAE.Animation standing = enemyTAE.Animations[0];

                TAE.Animation fadeIn = new TAE.Animation(10, standing.MiniHeader, standing.AnimFileName);
                fadeIn.CloneReference(standing, 0, true);
                TAE.Event fadeInEvent = new TAE.Event(0.0f, 2.0f, 193, 0, false, DS1[0][193]);
                fadeInEvent.Parameters["GhostVal1"] = 0.0f;
                fadeInEvent.Parameters["GhostVal2"] = 1.0f;
                fadeIn.Events.Add(fadeInEvent);
                enemyTAE.Animations.Add(fadeIn);

                TAE.Animation fadeOut = new TAE.Animation(11, standing.MiniHeader, standing.AnimFileName);
                fadeOut.CloneReference(standing, 0, true);
                TAE.Event fadeOutEvent = new TAE.Event(0.0f, 2.0f, 193, 0, false, DS1[0][193]);
                fadeOutEvent.Parameters["GhostVal1"] = 1.0f;
                fadeOutEvent.Parameters["GhostVal2"] = 0.0f;
                TAE.Event invisibleEvent = new TAE.Event(2.0f, 10.0f, 193, 0, false, DS1[0][193]);
                invisibleEvent.Parameters["GhostVal1"] = 0.0f;
                invisibleEvent.Parameters["GhostVal2"] = 0.0f;
                fadeOut.Events.Add(fadeOutEvent);
                fadeOut.Events.Add(invisibleEvent);
                enemyTAE.Animations.Add(fadeOut);
            }
        }
Example #2
0
            public TaeAnimPropertiesEdit(TAE.Animation anim, bool isMultiTaeSubID)
            {
                Title = "Edit Animation Properties";

                IsMultiTaeSubID = isMultiTaeSubID;

                TaeAnimID_String = IsMultiTaeSubID ? anim.ID.ToString() : GetAnimIDString(anim.ID);
                TaeAnimID_Value  = anim.ID;

                TaeAnimName = anim.AnimFileName;
                if (anim.MiniHeader is TAE.Animation.AnimMiniHeader.ImportOtherAnim asImportOtherAnim)
                {
                    TaeAnimHeader = new TAE.Animation.AnimMiniHeader.ImportOtherAnim()
                    {
                        ImportFromAnimID = asImportOtherAnim.ImportFromAnimID,
                        Unknown          = asImportOtherAnim.Unknown,
                    };
                    ImportFromAnimID_String = GetAnimIDString(asImportOtherAnim.ImportFromAnimID);
                    ImportFromAnimID_Value  = asImportOtherAnim.ImportFromAnimID;
                }
                else if (anim.MiniHeader is TAE.Animation.AnimMiniHeader.Standard asStandard)
                {
                    TaeAnimHeader = new TAE.Animation.AnimMiniHeader.Standard()
                    {
                        AllowDelayLoad        = asStandard.AllowDelayLoad,
                        ImportHKXSourceAnimID = asStandard.ImportHKXSourceAnimID,
                        ImportsHKX            = asStandard.ImportsHKX,
                        IsLoopByDefault       = asStandard.IsLoopByDefault,
                    };
                    ImportFromAnimID_String = GetAnimIDString(asStandard.ImportHKXSourceAnimID);
                    ImportFromAnimID_Value  = asStandard.ImportHKXSourceAnimID;
                }
            }
 public static void DrawAnimation(TAE.Animation animation, int framesPerSpace = 1, bool showArgNames = true, bool showTimes = true)
 {
     // Draw an animation's events on a timeline in ASCII. Increase `framesPerSpace` to compress the time axis.
     Console.WriteLine($"Animation {animation.ID}");
     foreach (var e in animation.Events)
     {
         int           startSpaces     = (int)Math.Round(e.StartTime * 30 / framesPerSpace, 0);
         float         durationSeconds = (e.EndTime - e.StartTime);
         int           durationSpaces  = (int)Math.Round(durationSeconds * 30 / framesPerSpace, 0);
         List <string> eventArgs       = new List <string>();
         if (e.Parameters != null)
         {
             foreach (var arg in e.Parameters.Values)
             {
                 eventArgs.Add(showArgNames ? $"{arg.Key}={arg.Value}" : $"{arg.Value}");
             }
         }
         string eventArgString = (e.Parameters != null) ? "(" + string.Join <string>(", ", eventArgs) + ")" : "";
         string eventLabel     = new string(' ', startSpaces) + $"{e.Type} {e.TypeName}{eventArgString}";
         if (showTimes)
         {
             eventLabel += $" [{e.StartTime:0.00} -> {e.EndTime:0.00}]";
         }
         string eventTimeline = new string(' ', startSpaces) + "|" + new string('>', durationSpaces);
         Console.WriteLine(eventLabel);
         Console.WriteLine(eventTimeline);
     }
 }
        public static float GetRealTAETime(TAE.Animation animation, float time, Dictionary <int, float> speedEffects)
        {
            // Looks for TAE events that apply speed-affecting SpEffect IDs in the given dictionary
            // and returns the real "wall time" of the given TAE animation time, relative to animation start.
            List <TAE.Event> speedEvents = new List <TAE.Event>(animation.Events.Where(
                                                                    e => e.Type == 66 && speedEffects.ContainsKey(Convert.ToInt32(e.Parameters["SpEffectID"]))));
            int   frame     = 0;
            float frameTime = frame / 30.0f;
            float realTime  = 0;

            while (frameTime < time)
            {
                float actualSpeed = 1;
                foreach (TAE.Event speedEvent in speedEvents)
                {
                    if (speedEvent.StartTime <= frameTime && frameTime < speedEvent.EndTime)
                    {
                        if (actualSpeed != 1)
                        {
                            throw new ArgumentException($"Animation contains multiple overlapping speed effects at time {frameTime}.");
                        }
                        int spEffectID = Convert.ToInt32(speedEvent.Parameters["SpEffectID"]);
                        actualSpeed *= speedEffects[spEffectID];
                    }
                }
                realTime += (1 / 30.0f) / actualSpeed;
                frame    += 1;
                frameTime = frame / 30.0f;
            }
            return(realTime);
        }
 public TaeEditAnimPropertiesForm(TAE.Animation animRef, bool isAbsoluteAnimID)
 {
     IsAbsoluteAnimID    = isAbsoluteAnimID;
     AnimRef             = animRef;
     originalID          = animRef.ID;
     originalMiniHeader  = animRef.MiniHeader.GetClone();
     originalDisplayName = animRef.AnimFileName;
     InitializeComponent();
 }
Example #6
0
        public static void SetIsModified(this TAE.Animation ev, bool v)
        {
            if (!isModified.ContainsKey(ev))
            {
                isModified.Add(ev, false);
            }

            isModified[ev] = v;
        }
Example #7
0
        public static bool GetIsModified(this TAE.Animation ev)
        {
            if (!isModified.ContainsKey(ev))
            {
                isModified.Add(ev, false);
            }

            return(isModified[ev]);
        }
Example #8
0
 public TaeEditAnimPropertiesForm(TAE.Animation animRef)
 {
     AnimRef             = animRef;
     originalID          = animRef.ID;
     originalIsReference = animRef.AnimFileReference;
     originalFileName    = animRef.AnimFileName;
     originalUnknown1    = animRef.Unknown1;
     originalUnknown2    = animRef.Unknown2;
     InitializeComponent();
 }
 void ApplySpeedAttackDamageMultiplier(TAE.Animation animation, float attackPowerScale, float startTime, float endTime)
 {
     // Rounds attackPowerScale to nearest multiple of 0.05.
     attackPowerScale = (int)Math.Round(20 * attackPowerScale) / 20.0f;
     if (!AttackDamageEffects.ContainsKey(attackPowerScale))
     {
         throw new KeyNotFoundException($"No attack effect with multiplier {attackPowerScale}");
     }
     animation.ApplyEffect(AttackDamageEffects[attackPowerScale], startTime, endTime);
 }
        public NPCAnimationInfo(TAE.Animation anim, int characterID, SoulsMod mod)
        {
            ChrHandler chrHandler = mod[characterID];

            if (chrHandler.ID == 0)
            {
                throw new ArgumentException("`NPCAnimationInfo` must receive a non-player `chrHandler`.");
            }
            InvokeAttackEvent     = anim.FindEvent(1);
            AllInvokeAttackEvents = new List <TAE.Event>(anim.Events.Where(e => e.Type == 1));
            InvokeBulletEvent     = anim.FindEvent(2);
            AllInvokeBulletEvents = new List <TAE.Event>(anim.Events.Where(e => e.Type == 2));
            PushEvent             = anim.FindEvent(307);

            CurrentAnim = anim;
            Mod         = mod;

            IsDeath = anim.FindJumpTable(12) != null;
            IsMove  = anim.InRange(200, 599);
            IsDash  = DashIds.Contains(anim.ID);
            IsThrow = chrHandler.ThrowAnimIds.Contains((int)anim.ID);
            HasInvokeBehaviorEvent = InvokeAttackEvent != null || InvokeBulletEvent != null || PushEvent != null;
            IsInjury = anim.InRange(2000, 2299);

            TAE.Event animationCancelEvent = anim.FindJumpTable(86);
            if (animationCancelEvent != null)
            {
                AnimationCancelEventEnd = animationCancelEvent.EndTime;
            }

            if (AllInvokeAttackEvents.Count != 0)
            {
                foreach (var invokeAttackEvent in AllInvokeAttackEvents)
                {
                    int  behaviorSubId   = Convert.ToInt32(invokeAttackEvent.Parameters["BehaviorSubID"]);
                    long behaviorParamId = 200000000 + 10000 * chrHandler.ID + behaviorSubId;
                    AllAttackBehaviorParamIds.Add(behaviorParamId);
                }
                AttackBehaviorParamId = AllAttackBehaviorParamIds[0];
            }

            if (AllInvokeBulletEvents.Count != 0)
            {
                foreach (var invokeBulletEvent in AllInvokeBulletEvents)
                {
                    int  behaviorSubId   = Convert.ToInt32(invokeBulletEvent.Parameters["BehaviorSubID"]);
                    long behaviorParamId = 200000000 + 10000 * chrHandler.ID + behaviorSubId;
                    AllBulletBehaviorParamIds.Add(behaviorParamId);
                    int behaviorDummyPolyId = Convert.ToInt32(invokeBulletEvent.Parameters["DummyPolyID"]);
                    AllBulletDummyPolyIds.Add(behaviorDummyPolyId);
                }
                BulletBehaviorParamId = AllBulletBehaviorParamIds[0];
                BulletDummyPolyId     = AllBulletDummyPolyIds[0];
            }
        }
Example #11
0
        public static void ShowTaeAnimPropertiesEditor(TAE.Animation anim)
        {
            Task.Run(new Action(() =>
            {
                var dlg       = new Dialog.TaeAnimPropertiesEdit(anim, Tae.FileContainer.AllTAEDict.Count > 1);
                dlg.OnDismiss = () =>
                {
                    if (dlg.CancelType == Dialog.CancelTypes.ClickedAcceptButton)
                    {
                        if (dlg.WasAnimDeleted)
                        {
                            if (Tae.SelectedTae.Animations.Count <= 1)
                            {
                                DialogOK("Can't Delete Last Animation",
                                         "Cannot delete the only animation remaining in the TAE.");
                            }
                            else
                            {
                                var indexOfCurrentAnim = Tae.SelectedTae.Animations.IndexOf(Tae.SelectedTaeAnim);
                                Tae.SelectedTae.Animations.Remove(Tae.SelectedTaeAnim);

                                Tae.RecreateAnimList();
                                Tae.UpdateSelectedTaeAnimInfoText();

                                if (indexOfCurrentAnim > Tae.SelectedTae.Animations.Count - 1)
                                {
                                    indexOfCurrentAnim = Tae.SelectedTae.Animations.Count - 1;
                                }

                                if (indexOfCurrentAnim >= 0)
                                {
                                    Tae.SelectNewAnimRef(Tae.SelectedTae, Tae.SelectedTae.Animations[indexOfCurrentAnim]);
                                }
                                else
                                {
                                    Tae.SelectNewAnimRef(Tae.SelectedTae, Tae.SelectedTae.Animations[0]);
                                }

                                Tae.SelectedTae.SetIsModified(!Tae.IsReadOnlyFileMode);
                            }
                        }
                        else
                        {
                            anim.MiniHeader   = dlg.TaeAnimHeader;
                            anim.ID           = dlg.TaeAnimID_Value.Value;
                            anim.AnimFileName = dlg.TaeAnimName;
                            anim.SetIsModified(true);
                            Tae.RecreateAnimList();
                            Tae.UpdateSelectedTaeAnimInfoText();
                        }
                    }
                };
                AddDialog(dlg);
            }));
        }
        public static void SetIsModified(this TAE.Animation ev, bool v, bool updateGui = true)
        {
            lock (isModified_Anim)
            {
                if (!isModified_Anim.ContainsKey(ev))
                {
                    isModified_Anim.Add(ev, false);
                }

                isModified_Anim[ev] = v;
            }
        }
 void ApplySpeedFunction(TAE.Animation animation, List <int> speedFunction)
 {
     for (int i = 0; i < speedFunction.Count; i++)
     {
         int   speedPoints     = speedFunction[i];
         float speedMultiplier = (float)Math.Round(speedPoints * QuantizedSpeed, 2);
         if (!SpeedEffects.ContainsKey(speedMultiplier))
         {
             throw new KeyNotFoundException($"No speed effect with multiplier {speedMultiplier}");
         }
         float startTime = Tools.SnapTimeTo30FPS(i * FrameDuration);
         float endTime   = startTime + FrameDuration; // effect lasts only one frame
         animation.ApplyEffect(SpeedEffects[speedMultiplier], startTime, endTime);
     }
 }
        //int GroupBraceThickness = 2;

        public void ScrollToAnimRef(TAE.Animation anim, bool scrollOnCenter)
        {
            if (anim == null)
            {
                return;
            }
            float verticalOffset = 0;
            float subOffset      = 0;

            foreach (var section in AnimTaeSections)
            {
                verticalOffset += AnimHeight; //section header
                if (section.InfoMap.ContainsKey(anim))
                {
                    // Un-collapse section where anim is.
                    if (section.Collapsed)
                    {
                        section.Collapsed = false;
                    }
                    subOffset = section.InfoMap[anim].VerticalOffset;
                    break;
                }

                if (!section.Collapsed)
                {
                    verticalOffset += section.HeightOfAllAnims;
                }
            }

            verticalOffset += subOffset;

            if (scrollOnCenter)
            {
                ScrollViewer.Scroll.Y = (verticalOffset + AnimHeight / 2f) - (ScrollViewer.Viewport.Height / 2f);
            }
            else
            {
                if (ScrollViewer.Scroll.Y > (verticalOffset - AnimHeight))
                {
                    ScrollViewer.Scroll.Y = verticalOffset - AnimHeight;
                }
                else if (ScrollViewer.Scroll.Y + Rect.Height < verticalOffset + (AnimHeight * 2))
                {
                    ScrollViewer.Scroll.Y = verticalOffset + (AnimHeight * 2) - Rect.Height;
                }
            }
        }
Example #15
0
        public static void SetIsModified(this TAE.Animation ev, bool v, bool updateGui = true)
        {
            lock (isModified_Anim)
            {
                if (!isModified_Anim.ContainsKey(ev))
                {
                    isModified_Anim.Add(ev, false);
                }

                //if (v)
                //{
                //    Console.WriteLine("REEE");
                //}

                isModified_Anim[ev] = v;

                if (updateGui)
                {
                    Main.TAE_EDITOR.UpdateIsModifiedStuff();
                }
            }
        }
Example #16
0
            public bool TryGetDetails(string animDataName, TAE.Animation anim, out string details)
            {
                if (cachedRegexToDetails == null)
                {
                    cachedRegexToDetails = (Details == null ? (IEnumerable <KeyValuePair <string, string> >) new KeyValuePair <string, string>[0] {
                    } : Details).Concat(Regex == null ? new KeyValuePair <string, string>[0] {
                    } : new[] { new KeyValuePair <string, string>(Regex, animDataName) }).ToDictionary(kvp => new Regex(kvp.Key), kvp => kvp.Value);
                }

                foreach (var kvp in cachedRegexToDetails)
                {
                    if (kvp.Key.IsMatch(anim.ID.ToString()))
                    {
                        details = kvp.Value;
                        return(true);
                    }
                }

                details = "not found";

                return(false);
            }
        void AddAnimationSpEffects()
        {
            // Add triggers for rolling.
            foreach (int dodgeAnimationId in RollAnimationIds)
            {
                TAE.Animation dodgeAnimation  = Mod.Player.GetAnimation(dodgeAnimationId);
                float         behaviorEndTime = dodgeAnimation.FindEvent(307).EndTime;
                dodgeAnimation.ApplyEffect(SpEffectGenerator.Effects["Rolling Damage (TAE TRIGGER)"], behaviorEndTime, behaviorEndTime + (1.0f / 30.0f));
            }

            // Add triggers for taking a hit while guarding.
            foreach (var(_, playerTAE) in Mod.Player.TAEs.Values)
            {
                foreach (int guardHitAnimationId in GuardHitAnimationIds)
                {
                    TAE.Animation guardHitAnimation = playerTAE.Animations.FirstOrDefault(a => a.ID == guardHitAnimationId);
                    if (guardHitAnimation != null)
                    {
                        // Apply trigger effect for one frame at the start of the animation.
                        guardHitAnimation.ApplyEffect(SpEffectGenerator.Effects["On Guard (TAE TRIGGER)"], 0.0f, 1.0f / 30.0f);
                    }
                }
            }
        }
        void AddFadingAnimations()
        {
            TAE.Animation standing = Mod.Player.TAEs[0].tae.Animations[0];

            TAE.Animation fadeIn = new TAE.Animation(10, standing.MiniHeader, standing.AnimFileName);
            fadeIn.CloneReference(standing, 0, true);
            TAE.Event fadeInEvent = new TAE.Event(0.0f, 2.0f, 193, 0, false, DS1[0][193]);
            fadeInEvent.Parameters["GhostVal1"] = 0.0f;
            fadeInEvent.Parameters["GhostVal2"] = 1.0f;
            fadeIn.Events.Add(fadeInEvent);
            Mod.Player.TAEs[0].tae.Animations.Add(fadeIn);

            TAE.Animation fadeOut = new TAE.Animation(11, standing.MiniHeader, standing.AnimFileName);
            fadeOut.CloneReference(standing, 0, true);
            TAE.Event fadeOutEvent = new TAE.Event(0.0f, 2.0f, 193, 0, false, DS1[0][193]);
            fadeOutEvent.Parameters["GhostVal1"] = 1.0f;
            fadeOutEvent.Parameters["GhostVal2"] = 0.0f;
            TAE.Event invisibleEvent = new TAE.Event(2.0f, 10.0f, 193, 0, false, DS1[0][193]);
            invisibleEvent.Parameters["GhostVal1"] = 0.0f;
            invisibleEvent.Parameters["GhostVal2"] = 0.0f;
            fadeOut.Events.Add(fadeOutEvent);
            fadeOut.Events.Add(invisibleEvent);
            Mod.Player.TAEs[0].tae.Animations.Add(fadeOut);
        }
 public TaeEventGroupRegion(TaeEditAnimEventGraph graph, TAE.Animation anim, TAE.EventGroup group)
 {
     Graph   = graph;
     TaeAnim = anim;
     Group   = group;
 }
        void RandomizeBehaviorAnimationSpeed(TAE.Animation behaviorAnim, NPCAnimationInfo info, bool isPlayer = false)
        {
            /* Apply random speed modifiers throughout given attack animation (includes behaviors that trigger Bullets and SpEffects).
             *  - The earliest possible time for the hitbox behavior to start is Min(oldStartTime, Max(0.5, 0.5 * oldStartTime, hitboxRadius)).
             *  - The latest possible time for the hitbox is 0.5 * (oldStart - minStart), clamped between [minMax] and [maxMax].
             *  - RoryAlgorithm is more likely to add speed to valid frames that already have more speed, which leads to less noisy functions.
             *  - Hitbox radius of bullets is estimated, but does not take projectile speed into account. Fast, small bullets may see large speed boosts.
             *  - Non-Bullet attack damage is scaled by [minAttackScale] (if earliest possible time) to [maxAttackScale] (if latest possible time), rounded to the nearest 0.1.
             *  - Bullets themselves are unchanged here and randomized separately.
             */
            float hitboxRadius      = 1.0f; // default
            float behaviorStartTime = -1.0f;
            float behaviorEndTime   = -1.0f;

            if (info.InvokeAttackEvent != null)
            {
                Behavior attackBehavior = info.GetAttackBehavior();
                if (attackBehavior == null)
                {
                    return;                          // Missing Behavior param, which means it is most likely unused. No randomization.
                }
                Attack attack = info.GetAttack();
                if (attack == null)
                {
                    return;                  // Missing Attack param, which means it is most likely unused. No randomization.
                }
                hitboxRadius      = attack.Hitbox0Radius;
                behaviorStartTime = info.InvokeAttackEvent.StartTime;
                behaviorEndTime   = info.InvokeAttackEvent.EndTime;
            }
            else if (info.InvokeBulletEvent != null)
            {
                Behavior bulletBehavior = info.GetBulletBehavior();
                if (bulletBehavior == null)
                {
                    return;                          // Missing Behavior param, which means it is most likely unused. No randomization.
                }
                if (bulletBehavior.ReferenceType == 1)
                {
                    Bullet bullet = info.GetBullet();
                    if (bullet == null)
                    {
                        return;                  // Missing Bullet param.
                    }
                    hitboxRadius = Tools.GuessBulletRadius(bullet, Mod);
                    // Console.WriteLine($"    Final bullet ({bullet.ID}) radius of animation {behaviorAnim.ID}: {hitboxRadius:0.00}");
                    if (hitboxRadius == -1.0f)
                    {
                        hitboxRadius = 1.0f;                         // default for bullets with no final radius
                    }
                }
                else if (bulletBehavior.ReferenceType == 2)
                {
                    SpEffect spEffect = info.GetSpEffect();
                    if (spEffect == null)
                    {
                        return;           // Missing SpEffect param.
                    }
                    hitboxRadius = 1.0f;  // leave as default for SpEffect
                }
                behaviorStartTime = info.InvokeBulletEvent.StartTime;
                behaviorEndTime   = info.InvokeBulletEvent.EndTime;
            }
            if (behaviorStartTime == -1.0f || behaviorEndTime == -1.0f)
            {
                throw new ArgumentException($"Behavior start/end times were not set.");
            }

            float      minBehaviorStartTime   = Math.Min(behaviorStartTime, Math.Max(0.5f, Math.Max(0.5f * behaviorStartTime, MinAttackBehaviorHitboxScalar * hitboxRadius)));
            float      maxBehaviorStartTime   = behaviorStartTime + Math.Max(MinMaxAttackBehaviorDelay, Math.Min(0.5f * (behaviorStartTime - minBehaviorStartTime), MaxMaxAttackBehaviorDelay));
            float      newBehaviorStartTime   = minBehaviorStartTime + (float)Rand.NextDouble() * (maxBehaviorStartTime - minBehaviorStartTime);
            List <int> preAttackSpeedFunction = GetRandomSpeedFunction(0.0f, behaviorStartTime, newBehaviorStartTime);

            ApplySpeedFunction(behaviorAnim, preAttackSpeedFunction);
            if (DEBUG)
            {
                Console.WriteLine($"\nANIMATION {behaviorAnim.ID}");
                Console.WriteLine($"    Attack start time: {behaviorStartTime} => {newBehaviorStartTime}");
                Console.WriteLine($"    Random min/max: {minBehaviorStartTime}, {maxBehaviorStartTime}");
                Tools.DrawSpeedFunction(preAttackSpeedFunction, MaxSpeedMultiplier, QuantizedSpeed);
            }
            if (info.InvokeAttackEvent != null)
            {
                float attackPowerFactor      = (newBehaviorStartTime - minBehaviorStartTime) / (maxBehaviorStartTime - minBehaviorStartTime);
                float attackDamageMultiplier = MinAttackScale + attackPowerFactor * (MaxAttackScale - MinAttackScale);
                ApplySpeedAttackDamageMultiplier(behaviorAnim, attackDamageMultiplier, behaviorStartTime - FrameDuration, behaviorEndTime + FrameDuration);
            }
            // Mild random speed change during InvokeBehaviorEvent itself (multiplier of 1.0, 1.1, or 1.2).
            int   duringSpeedOptionCount = (int)(0.2f / QuantizedSpeed) + 1;
            float duringAttackSpeed      = 1.0f + (Rand.Next(duringSpeedOptionCount) * QuantizedSpeed);
            int   duringSpEffectID       = SpeedEffects[(float)Math.Round(duringAttackSpeed, 2)];

            behaviorAnim.ApplyEffect(duringSpEffectID, behaviorStartTime, behaviorEndTime);

            // Post-attack speed function (if cancel event is present to approximate end of animation).
            if (info.AnimationCancelEventEnd != -1.0f)
            {
                float animationEndTime    = info.AnimationCancelEventEnd;
                float minEndRealTime      = behaviorEndTime + (0.5f * (animationEndTime - behaviorEndTime));
                float maxEndRealTime      = behaviorEndTime + (1.3f * (animationEndTime - behaviorEndTime));
                float newAnimationEndTime = minEndRealTime + (float)Rand.NextDouble() * (maxEndRealTime - minEndRealTime);
                if (DEBUG)
                {
                    Console.WriteLine($"    Attack end time: {behaviorEndTime} (before pre-attack speed change)");
                    Console.WriteLine($"    Animation end time: {animationEndTime} => {newAnimationEndTime}");
                    Console.WriteLine($"    Random min/max: {minEndRealTime}, {maxEndRealTime}");
                }
                List <int> postAttackSpeedFunction = GetRandomSpeedFunction(behaviorEndTime, animationEndTime, newAnimationEndTime);
                ApplySpeedFunction(behaviorAnim, postAttackSpeedFunction);
            }
        }
 public static float GetRealEventEndTime(TAE.Animation animation, TAE.Event taeEvent, Dictionary <int, float> speedEffects)
 {
     return(GetRealTAETime(animation, taeEvent.EndTime, speedEffects));
 }