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