using Cthangover.Core.UI.Event;
using Godot;
namespace Cthangover.Mods.Effects.Animation
{
///
/// Shader-driven frame animation: drives _FrameIndex and _FrameCount uniforms
/// on the TextureRect's ShaderMaterial rather than swapping textures. This means
/// all animation frames live in a single GPU texture atlas and the shader selects
/// the visible frame. Auto-destroys (QueueFree) after totalDuration elapses,
/// making it suitable for fire-and-forget VFX that don't need external cleanup.
///
public partial class EffectAnimator : Control, IOnUpdateEvent
{
[Export] private TextureRect image;
[Export] private float totalDuration = 5f;
[Export] private int frameCount = 5;
private ShaderMaterial materialInstance;
private double startTime;
private float frameInterval;
private int currentFrame;
private static readonly StringName FrameIndexId = "_FrameIndex";
private static readonly StringName FrameCountId = "_FrameCount";
/// Precomputes the frame interval and pushes the frame count uniform into the shader material.
public override void _Ready()
{
frameInterval = totalDuration / frameCount;
materialInstance = image.Material as ShaderMaterial;
if (materialInstance != null)
materialInstance.SetShaderParameter(FrameCountId, frameCount);
}
/// Intentionally empty — the animator drives frames through the event controller instead of engine callbacks.
public override void _EnterTree()
{
}
/// Starts the shader animation from frame 0, resetting the elapsed timer.
public void Play()
{
startTime = Godot.Time.GetTicksUsec() / 1_000_000.0;
currentFrame = 0;
if (materialInstance != null)
materialInstance.SetShaderParameter(FrameIndexId, 0f);
}
/// Update priority. Default 1.
public int Priority => 1;
/// Advances the shader's _FrameIndex uniform each frame based on elapsed time. Self-destructs when totalDuration elapses.
public void OnUpdate()
{
double now = Godot.Time.GetTicksUsec() / 1_000_000.0;
var elapsed = now - startTime;
var newFrame = Mathf.Min((int)(elapsed / frameInterval), frameCount - 1);
if (newFrame != currentFrame && materialInstance != null)
{
currentFrame = newFrame;
materialInstance.SetShaderParameter(FrameIndexId, currentFrame);
}
if (elapsed >= totalDuration)
QueueFree();
}
}
}