using Cthangover.Core.Battle.Actions; using Cthangover.Core.Characters; using Cthangover.FFBattle.UI; using Godot; namespace Cthangover.FFBattle.Actions { /// /// Abstract base for all FF battle animations. Implements a three-phase lifecycle /// (, , ) driven /// by the battle core's per-frame loop. Subclasses implement their animation logic /// in DoInternalStart/DoInternalAction/DoInternalEnd and are instantiated /// by when a player or enemy action is executed. /// The multiplier scales all timing, used e.g. to speed up /// enemy turn animations (0.8f). /// public abstract class FFAbstractAnimation : IBattleAction { /// Start time of the current animation phase, in seconds (microsecond precision). protected double Timestamp { get; set; } = -1; /// Animation speed multiplier; lower values produce faster animations. protected float Speed { get; set; } = 1f; /// The action descriptor being executed (determines which executor runs). protected ActionCharacter Action { get; set; } /// The character widget performing the action. protected FFCharacterWidget Source { get; set; } /// The character widget receiving the action. protected FFCharacterWidget Target { get; set; } /// World position of captured at . protected Vector2 SourcePos { get; set; } /// World position of captured at . protected Vector2 TargetPos { get; set; } /// Creates an animation binding a source, target, and action with an optional speed override. protected FFAbstractAnimation(FFCharacterWidget source, FFCharacterWidget target, ActionCharacter action, float speed = 1f) { Source = source; Target = target; Action = action; Speed = speed; } /// /// Advances the animation by one frame. Returns true when the animation /// sequence has completed. Called in a tight while loop by /// each process frame. /// public bool DoAction() { if (Source == null || Target == null || Action == null) return true; // The target's death animation may free its widget mid-animation; // finish early and let DoEnd snap the source back. if (!IsAlive(Source) || !IsAlive(Target)) return true; return DoInternalAction(); } /// True while the widget is still in the tree and not queued for deletion. protected static bool IsAlive(FFCharacterWidget widget) => GodotObject.IsInstanceValid(widget) && !widget.IsQueuedForDeletion(); /// Captures the initial world positions of source and target, then delegates to subclass setup. public void DoStart() { if (Source == null || Target == null || Action == null) return; SourcePos = Source.GlobalPosition; TargetPos = Target.GlobalPosition; DoInternalStart(); } /// Restores source to original position and delegates cleanup to subclass. public void DoEnd() { if (Source == null || Target == null || Action == null) return; DoInternalEnd(); } /// Frame tick of the animation; implementors advance phases and return true when finished. protected abstract bool DoInternalAction(); /// Phase setup called once by after positions are captured. protected abstract void DoInternalStart(); /// Cleanup called once by — restore captured positions here. protected abstract void DoInternalEnd(); /// Quadratic ease-out: fast start, decelerating to the target. Used for return-to-start motion. protected float EaseOutQuad(float t) => 1f - (1f - t) * (1f - t); /// Quadratic ease-in-out: slow start and end, fast in the middle. Used for approach motion. protected float EaseInOutQuad(float t) => t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f; } }