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