Chessistics/Scripts/Presentation/EventAnimator.cs

299 lines
10 KiB
C#
Raw Normal View History

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Chessistics.Engine.Events;
using Chessistics.Engine.Model;
using Chessistics.Scripts.Board;
using Chessistics.Scripts.Pieces;
using Chessistics.Scripts.UI;
namespace Chessistics.Scripts.Presentation;
public partial class EventAnimator : Node
{
private BoardView _boardView = null!;
private ObjectivePanel _objectivePanel = null!;
private ControlBar _controlBar = null!;
private MetricsOverlay _metricsOverlay = null!;
private readonly Dictionary<int, PieceView> _pieceViews = new();
private readonly Dictionary<int, TrajectView> _trajectViews = new();
private bool _animating;
public bool IsAnimating => _animating;
private static readonly Color WoodCargoColor = new("#8B6914");
private static readonly Color StoneCargoColor = new("#808080");
private const float ProduceDuration = 0.3f;
private const float TransferDuration = 0.25f;
private const float MoveDuration = 0.3f;
private const float KnightMoveDuration = 0.4f;
private const float DestroyDuration = 0.3f;
[Signal]
public delegate void TurnAnimationCompletedEventHandler();
[Signal]
public delegate void VictoryReachedEventHandler();
public void Initialize(BoardView boardView, ObjectivePanel objectivePanel,
ControlBar controlBar, MetricsOverlay metricsOverlay)
{
_boardView = boardView;
_objectivePanel = objectivePanel;
_controlBar = controlBar;
_metricsOverlay = metricsOverlay;
}
public void RegisterPiece(int pieceId, PieceView pieceView, TrajectView trajectView)
{
_pieceViews[pieceId] = pieceView;
_trajectViews[pieceId] = trajectView;
}
public void UnregisterPiece(int pieceId)
{
if (_pieceViews.TryGetValue(pieceId, out var pv))
{
pv.QueueFree();
_pieceViews.Remove(pieceId);
}
if (_trajectViews.TryGetValue(pieceId, out var tv))
{
tv.QueueFree();
_trajectViews.Remove(pieceId);
}
}
public void ProcessEvents(IReadOnlyList<IWorldEvent> events)
{
_animating = true;
var tween = CreateTween();
tween.SetParallel(false);
var produceEvents = new List<CargoProducedEvent>();
var transferEvents = new List<IWorldEvent>();
var moveEvents = new List<PieceMovedEvent>();
var collisionEvents = new List<PieceDestroyedEvent>();
foreach (var evt in events)
{
switch (evt)
{
case TurnStartedEvent ts:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() => _controlBar.UpdateTurn(ts.TurnNumber)));
break;
case CargoProducedEvent produced:
produceEvents.Add(produced);
break;
case CargoTransferredEvent:
case DemandProgressEvent:
transferEvents.Add(evt);
break;
case PieceMovedEvent moved:
moveEvents.Add(moved);
break;
case PieceDestroyedEvent destroyed:
collisionEvents.Add(destroyed);
break;
case VictoryEvent victory:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() =>
{
_metricsOverlay.ShowMetrics(victory.Metrics);
EmitSignal(SignalName.VictoryReached);
}));
break;
case TurnEndedEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
break;
default:
break;
}
}
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() =>
{
_animating = false;
EmitSignal(SignalName.TurnAnimationCompleted);
}));
}
private void FlushPhases(
Tween tween,
List<CargoProducedEvent> produceEvents,
List<IWorldEvent> transferEvents,
List<PieceMovedEvent> moveEvents,
List<PieceDestroyedEvent> collisionEvents)
{
// Phase 1: Produce — flash production cells
if (produceEvents.Count > 0)
{
tween.TweenCallback(Callable.From(() =>
{
foreach (var evt in produceEvents.ToList())
{
var cell = _boardView.GetCellView(evt.ProductionCell);
cell?.FlashProduce(ProduceDuration);
}
}));
tween.TweenInterval(ProduceDuration);
produceEvents.Clear();
}
// Phase 2: Transfers — animate cargo sliding from giver to receiver
if (transferEvents.Count > 0)
{
// Capture the events list before clearing
var eventsToAnimate = transferEvents.ToList();
// Step 1: remove cargo from givers + spawn sliding cargo sprites
tween.TweenCallback(Callable.From(() =>
{
foreach (var evt in eventsToAnimate)
{
if (evt is CargoTransferredEvent transfer)
{
// Remove cargo indicator from giver
if (transfer.GivingPieceId != null && _pieceViews.ContainsKey(transfer.GivingPieceId.Value))
_pieceViews[transfer.GivingPieceId.Value].SetCargo(null);
// Create sliding cargo sprite
SpawnCargoSlide(transfer);
}
}
}));
// Step 2: wait for slide, then show cargo on receivers + update demand progress
tween.TweenInterval(TransferDuration);
tween.TweenCallback(Callable.From(() =>
{
foreach (var evt in eventsToAnimate)
{
if (evt is CargoTransferredEvent transfer)
{
if (transfer.ReceivingPieceId != null && _pieceViews.ContainsKey(transfer.ReceivingPieceId.Value))
_pieceViews[transfer.ReceivingPieceId.Value].SetCargo(transfer.Type);
}
else if (evt is DemandProgressEvent progress)
{
_objectivePanel.UpdateProgress(progress.DemandCell, progress.Name, progress.Current, progress.Required);
}
}
}));
transferEvents.Clear();
}
// Phase 3: Movement — all pieces move simultaneously
if (moveEvents.Count > 0)
{
tween.SetParallel(true);
foreach (var moved in moveEvents)
{
if (_pieceViews.TryGetValue(moved.PieceId, out var pv))
{
var target = _boardView.CoordsToPixel(moved.To);
float duration = pv.Kind == PieceKind.Knight ? KnightMoveDuration : MoveDuration;
tween.TweenProperty(pv, "position", target, duration);
}
}
tween.SetParallel(false);
moveEvents.Clear();
}
// Phase 4: Collision/Destruction
if (collisionEvents.Count > 0)
{
tween.SetParallel(true);
foreach (var destroyed in collisionEvents)
{
var pieceId = destroyed.PieceId;
tween.TweenCallback(Callable.From(() =>
{
FlashPiece(pieceId);
UnregisterPiece(pieceId);
}));
}
tween.SetParallel(false);
tween.TweenInterval(DestroyDuration);
collisionEvents.Clear();
}
}
/// <summary>
/// Creates a temporary colored square that slides from the giver to the receiver.
/// </summary>
private void SpawnCargoSlide(CargoTransferredEvent transfer)
{
var from = _boardView.CoordsToPixel(transfer.From);
var to = _boardView.CoordsToPixel(transfer.To);
var color = transfer.Type switch
{
CargoType.Wood => WoodCargoColor,
CargoType.Stone => StoneCargoColor,
_ => Colors.White
};
var sprite = new ColorRect
{
Size = new Vector2(14, 14),
Position = new Vector2(-7, -7),
Color = color,
MouseFilter = Control.MouseFilterEnum.Ignore
};
var container = new Node2D { Position = from };
container.AddChild(sprite);
_boardView.AddChild(container);
var slideTween = container.CreateTween();
slideTween.TweenProperty(container, "position", to, TransferDuration)
.SetEase(Tween.EaseType.InOut)
.SetTrans(Tween.TransitionType.Cubic);
slideTween.TweenCallback(Callable.From(() => container.QueueFree()));
}
private void FlashPiece(int pieceId)
{
if (!_pieceViews.TryGetValue(pieceId, out var pv)) return;
var tween = pv.CreateTween();
tween.TweenProperty(pv, "modulate", new Color(1, 0.2f, 0.2f), 0.1f);
tween.TweenProperty(pv, "modulate", Colors.White, 0.1f);
tween.SetLoops(3);
}
public void ResetPiecePositions(BoardSnapshot snapshot)
{
foreach (var ps in snapshot.Pieces)
{
if (_pieceViews.TryGetValue(ps.Id, out var pv))
{
pv.Position = _boardView.CoordsToPixel(ps.StartCell);
pv.SetCargo(null);
}
}
}
public void ClearAll()
{
foreach (var pv in _pieceViews.Values)
pv.QueueFree();
foreach (var tv in _trajectViews.Values)
tv.QueueFree();
_pieceViews.Clear();
_trajectViews.Clear();
}
}