void AddSpeedEffects() { // Adds speed modifier SpEffects (9000 range) to GPARAM and registers in dictionary for easy lookup here. int speedEffectCount = (int)(MaxSpeedMultiplier / QuantizedSpeed); for (int i = 1; i <= speedEffectCount; i++) { float speed = (float)Math.Round(i * QuantizedSpeed, 2); SpEffect effect = Mod.GPARAM.SpEffects.CopyRow(81001, 9000 + i); effect.Name = $"SpeedMultiplier ({speed:0.00}x)"; effect.EffectDuration = 0; effect.SpecialState = 0; effect.SpecialEffectCategory = 0; effect.AnimationSpeedMultiplier = speed; effect.CanAffectAll = true; SpeedEffects[speed] = Convert.ToInt32(effect); } }
void AddAttackDamageEffects() { // Adds blanket attack damage modifier SpEffects (9100 range) to GPARAM and registers in dictionary for easy lookup here. int attackEffectCount = (int)(2.0f / 0.05f); for (int i = 1; i <= attackEffectCount; i++) { float attackMultiplier = (float)Math.Round(i * 0.05, 2); SpEffect effect = Mod.GPARAM.SpEffects.CopyRow(81001, 9100 + i); effect.Name = $"AttackDamageMultiplier ({attackMultiplier:0.00}x)"; effect.EffectDuration = 0; effect.OutgoingPhysicalDamageMultiplier = attackMultiplier; effect.OutgoingMagicDamageMultiplier = attackMultiplier; effect.OutgoingFireDamageMultiplier = attackMultiplier; effect.OutgoingLightningDamageMultiplier = attackMultiplier; effect.OutgoingStaminaDamageMultiplier = attackMultiplier; effect.CanAffectAll = true; AttackDamageEffects[attackMultiplier] = Convert.ToInt32(effect); } }
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); } }