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); } }
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(); }
public static void SetIsModified(this TAE.Animation ev, bool v) { if (!isModified.ContainsKey(ev)) { isModified.Add(ev, false); } isModified[ev] = v; }
public static bool GetIsModified(this TAE.Animation ev) { if (!isModified.ContainsKey(ev)) { isModified.Add(ev, false); } return(isModified[ev]); }
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]; } }
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; } } }
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(); } } }
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)); }