using System.Collections.Generic;
using UnityEngine;
using Vanguard.Battle;
using Vanguard.Board3D;
namespace Vanguard.Board3D
{
///
/// Единый планировщик карточных перемещений. Карточки анимируются
/// Card3D, аниматор отвечает за stagger, очередь и InputLock.
///
public sealed class CardMoveAnimator
{
private sealed class Request
{
public Card3D Card;
public Vector3 FromPosition;
public Quaternion FromRotation;
public Vector3 FromScale;
public float Duration;
public float ArcHeight;
}
private sealed class ActiveMove
{
public Card3D Card;
public float Remaining;
}
private readonly Queue _queue = new Queue();
private readonly List _active = new List();
private readonly InputLockService _inputLock;
private AnimationConfig _config;
private float _delay;
private bool _locked;
public CardMoveAnimator(
InputLockService inputLock,
AnimationConfig config)
{
_inputLock = inputLock;
_config = config;
}
public bool IsBusy => _queue.Count > 0 || _active.Count > 0;
public void SetConfig(AnimationConfig config)
{
_config = config;
}
public void Enqueue(
Card3D card,
Vector3 fromPosition,
Quaternion fromRotation,
Vector3 fromScale,
float duration,
float arcHeight)
{
if (card == null || card.IsDragging)
return;
EnsureLock();
_queue.Enqueue(new Request
{
Card = card,
FromPosition = fromPosition,
FromRotation = fromRotation,
FromScale = fromScale,
Duration = duration,
ArcHeight = arcHeight,
});
}
public void Tick(float deltaTime)
{
if (_active.Count > 0)
{
var delta = Mathf.Max(0f, deltaTime);
for (var i = _active.Count - 1; i >= 0; i--)
{
var active = _active[i];
var card = active?.Card;
if (active == null
|| card == null
|| !card.gameObject.activeInHierarchy)
{
if (card != null)
card.StopMove(snap: false);
_active.RemoveAt(i);
continue;
}
active.Remaining -= delta;
if (!card.IsMoving)
{
_active.RemoveAt(i);
continue;
}
// Страховка: карта могла быть снята с рендера до
// завершения Tick. Не держим InputLock вечно.
if (active.Remaining <= 0f)
{
card.StopMove(snap: true);
_active.RemoveAt(i);
}
}
}
if (_queue.Count > 0)
{
_delay -= Mathf.Max(0f, deltaTime);
if (_delay <= 0f)
StartNext();
}
if (!IsBusy)
ReleaseLock();
}
public void CancelAll(bool snapCards = false)
{
_queue.Clear();
foreach (var active in _active)
{
if (active?.Card != null)
active.Card.StopMove(snapCards);
}
_active.Clear();
_delay = 0f;
ReleaseLock();
}
public void CancelCard(Card3D card)
{
if (card == null)
return;
var remaining = new Queue();
while (_queue.Count > 0)
{
var request = _queue.Dequeue();
if (request.Card != card)
remaining.Enqueue(request);
}
while (remaining.Count > 0)
_queue.Enqueue(remaining.Dequeue());
for (var i = _active.Count - 1; i >= 0; i--)
{
if (_active[i]?.Card != card)
continue;
card.StopMove(snap: false);
_active.RemoveAt(i);
}
if (!IsBusy)
ReleaseLock();
}
private void StartNext()
{
if (_queue.Count == 0)
return;
var request = _queue.Dequeue();
var card = request.Card;
if (card == null || card.IsDragging)
{
_delay = 0f;
return;
}
card.StartArcMove(
request.FromPosition,
request.FromRotation,
request.FromScale,
request.Duration,
request.ArcHeight);
if (card.IsMoving)
{
_active.Add(new ActiveMove
{
Card = card,
Remaining = Mathf.Max(0.1f, request.Duration) + 0.05f,
});
}
_delay = Mathf.Max(0.01f, _config != null
? _config.moveDelayBetweenCards
: 0.18f);
}
private void EnsureLock()
{
if (_locked)
return;
_inputLock?.Acquire("card-move");
_locked = true;
}
private void ReleaseLock()
{
if (!_locked)
return;
_inputLock?.Release("card-move");
_locked = false;
}
}
}