using Cthangover.Core.Characters;
using Cthangover.Core.Items;
using Cthangover.FFBattle.UI;
using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.FFBattle.Actions
{
///
/// Animation for item usage in battle. Creates a floating item icon
/// () that rises above the source character and then
/// flies toward the target with fading opacity. Phases: (1) RaiseItem — the
/// item icon floats upward and the source bobs, (2) ApplyEffect — the icon
/// lerps to the target's position and
/// is called at 30% phase, (3) Recover — source returns to position with
/// ease-out. The icon is freed in .
///
public class FFItemAnimation : FFAbstractAnimation
{
private enum Phase { RaiseItem, ApplyEffect, Recover, Done }
private Phase _phase;
private IItem _item;
private bool _effectApplied;
private TextureRect _itemIcon;
/// Creates an item-use animation. is a synthetic "ff/item" descriptor; actual logic runs via .
public FFItemAnimation(FFCharacterWidget source, FFCharacterWidget target, ActionCharacter action, IItem item, float speed = 1f)
: base(source, target, action, speed)
{
_item = item;
}
/// Spawns the raised item icon above the user and starts the item sequence.
protected override void DoInternalStart()
{
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
_effectApplied = false;
_phase = Phase.RaiseItem;
if (_item?.Sprite != null)
{
_itemIcon = new TextureRect();
_itemIcon.Texture = _item.Sprite;
_itemIcon.ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize;
_itemIcon.StretchMode = TextureRect.StretchModeEnum.KeepAspect;
_itemIcon.Size = new Vector2(32, 32);
_itemIcon.Position = Source.GlobalPosition - Source.Position + new Vector2(20, -40);
Source.AddChild(_itemIcon);
}
}
/// Advances raise-item → apply (item use with effect flash) → lower-icon.
protected override bool DoInternalAction()
{
var elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
switch (_phase)
{
case Phase.RaiseItem:
{
var progress = Mathf.Clamp(elapsed / 0.3f, 0f, 1f);
if (_itemIcon != null)
{
var iconStartPos = Source.GlobalPosition - Source.Position + new Vector2(20, -40);
_itemIcon.Position = iconStartPos + new Vector2(0, -30f * EaseInOutQuad(progress));
}
var bounce = Mathf.Sin(progress * Mathf.Pi) * 8f;
Source.GlobalPosition = SourcePos + new Vector2(0, -Mathf.Abs(bounce));
if (progress >= 1f)
{
_phase = Phase.ApplyEffect;
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
}
break;
}
case Phase.ApplyEffect:
{
var progress = Mathf.Clamp(elapsed / 0.35f, 0f, 1f);
if (_itemIcon != null && Target != null)
{
var localTarget = Target.GlobalPosition - Source.Position;
var iconStart = Source.GlobalPosition - Source.Position + new Vector2(20, -70);
_itemIcon.Position = iconStart.Lerp(localTarget + new Vector2(20, -20), EaseInOutQuad(progress));
_itemIcon.Modulate = UiTokens.Alpha(UiTokens.White, 1f - progress * 0.5f);
}
if (!_effectApplied && progress >= 0.3f)
{
_effectApplied = true;
if (_item != null)
FFItemExecutor.TryUseItem(_item, Source.Card, Target.Card);
Source.UpdateInfo();
Target.UpdateInfo();
Target.Flash(UiTokens.Success, 0.3f);
}
if (progress >= 1f)
{
_phase = Phase.Recover;
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
}
break;
}
case Phase.Recover:
{
var progress = Mathf.Clamp(elapsed / 0.25f, 0f, 1f);
Source.GlobalPosition = Source.GlobalPosition.Lerp(SourcePos, EaseOutQuad(progress));
if (progress >= 1f)
{
_phase = Phase.Done;
return true;
}
break;
}
}
return false;
}
/// Frees the item icon, returns the user to its original position and refreshes both widgets.
protected override void DoInternalEnd()
{
if (IsAlive(Source))
Source.GlobalPosition = SourcePos;
if (_itemIcon != null)
{
_itemIcon.QueueFree();
_itemIcon = null;
}
Source.UpdateInfo();
Target.UpdateInfo();
}
}
}