2026-04-10 14:58:03 +02:00
|
|
|
using Godot;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2026-04-10 21:44:12 +02:00
|
|
|
using System.Linq;
|
2026-04-10 14:58:03 +02:00
|
|
|
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;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
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;
|
|
|
|
|
|
2026-04-10 14:58:03 +02:00
|
|
|
[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);
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
var produceEvents = new List<CargoProducedEvent>();
|
|
|
|
|
var transferEvents = new List<IWorldEvent>();
|
|
|
|
|
var moveEvents = new List<PieceMovedEvent>();
|
|
|
|
|
var collisionEvents = new List<PieceDestroyedEvent>();
|
|
|
|
|
|
2026-04-10 14:58:03 +02:00
|
|
|
foreach (var evt in events)
|
|
|
|
|
{
|
|
|
|
|
switch (evt)
|
|
|
|
|
{
|
|
|
|
|
case TurnStartedEvent ts:
|
2026-04-10 21:44:12 +02:00
|
|
|
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
|
2026-04-10 14:58:03 +02:00
|
|
|
tween.TweenCallback(Callable.From(() => _controlBar.UpdateTurn(ts.TurnNumber)));
|
|
|
|
|
break;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
case CargoProducedEvent produced:
|
|
|
|
|
produceEvents.Add(produced);
|
2026-04-10 14:58:03 +02:00
|
|
|
break;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
case CargoTransferredEvent:
|
|
|
|
|
case DemandProgressEvent:
|
|
|
|
|
transferEvents.Add(evt);
|
2026-04-10 14:58:03 +02:00
|
|
|
break;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
case PieceMovedEvent moved:
|
|
|
|
|
moveEvents.Add(moved);
|
2026-04-10 14:58:03 +02:00
|
|
|
break;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
case PieceDestroyedEvent destroyed:
|
|
|
|
|
collisionEvents.Add(destroyed);
|
2026-04-10 14:58:03 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case VictoryEvent victory:
|
2026-04-10 21:44:12 +02:00
|
|
|
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
|
2026-04-10 14:58:03 +02:00
|
|
|
tween.TweenCallback(Callable.From(() =>
|
|
|
|
|
{
|
|
|
|
|
_metricsOverlay.ShowMetrics(victory.Metrics);
|
|
|
|
|
EmitSignal(SignalName.VictoryReached);
|
|
|
|
|
}));
|
|
|
|
|
break;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
case TurnEndedEvent:
|
|
|
|
|
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
|
2026-04-10 14:58:03 +02:00
|
|
|
break;
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
default:
|
2026-04-10 14:58:03 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
|
|
|
|
|
|
2026-04-10 14:58:03 +02:00
|
|
|
tween.TweenCallback(Callable.From(() =>
|
|
|
|
|
{
|
|
|
|
|
_animating = false;
|
|
|
|
|
EmitSignal(SignalName.TurnAnimationCompleted);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 21:44:12 +02:00
|
|
|
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()));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 14:58:03 +02:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|