ported from perforce

This commit is contained in:
2026-04-19 00:43:27 +02:00
commit 6c0c33f5d4
700 changed files with 19735 additions and 0 deletions

View File

@@ -0,0 +1,756 @@
using Godot;
using RobotAndDonkey.Game.Cards;
using RobotAndDonkey.Game.Modifiers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using RobotAndDonkey.Game;
public partial class CardRow : Control
{
public override void _Ready()
{
CacheBackgroundPanels();
UpdateBackgroundVisibility();
}
public override void _Process(double delta)
{
if (s_CardPressActive && !Input.IsMouseButtonPressed(MouseButton.Left))
s_CardPressActive = false;
if (OrderedCards.Count == 0)
{
m_LayoutDirty = false;
return;
}
var needsLayout = m_LayoutDirty || GlobalPosition != m_LastPosition || Size != m_LastSize;
if (!needsLayout)
return;
m_LayoutDirty = false;
LayoutCards();
if (GetParent() is not Container)
SetAnchorsPreset(LayoutPreset.Center);
}
public override void _GuiInput(InputEvent @event)
{
if (@event is InputEventMouseButton mouseButton && mouseButton.ButtonIndex == MouseButton.Left && mouseButton.Pressed)
ClearSelection();
}
public void Configure(IReadOnlyList<Card> cards, Tween tween, bool celebrateModifiers, bool drag)
{
Debug.WriteLine($"Configure {cards.Count} cards, celebrate={celebrateModifiers}, drag={drag}");
if (CardScene == null)
{
GD.PushWarning($"{nameof(CardRow)} has no CardScene assigned. No cards will be shown.");
return;
}
if (cards.SequenceEqual(OrderedCards))
return;
var newSet = new HashSet<Guid>(cards.Select(c => c.CardId));
var toRemove = OrderedCards.Where(c => !newSet.Contains(c.CardId)).ToList();
foreach (var card in toRemove)
{
if (!CardToControl.Remove(card.CardId, out var control))
continue;
control.QueueFree();
}
OrderedCards.Clear();
OrderedCards.AddRange(cards);
var newControls = new List<CardControl>();
for (var index = 0; index < cards.Count; index++)
{
var card = cards[index];
if (!CardToControl.TryGetValue(card.CardId, out var control))
{
Debug.WriteLine($"Add new card {card.Id}");
control = CreateCardControl(card);
CardToControl.Add(card.CardId, control);
AddChild(control);
newControls.Add(control);
}
else
{
control.Configure(card, this);
}
}
LayoutCards();
foreach (var control in newControls)
control.SnapToDesired();
if (celebrateModifiers)
{
foreach (var card in cards)
{
foreach (var modifier in card.Modifiers)
{
ModifyCard(card, modifier.Id, tween);
}
}
}
}
private void GetLayoutMetrics(Card card, out float visualWidth, out float leftOffset, out float topOffset, out float bottomOffset)
{
var size = CardSize;
var desiredScale = Vector2.One;
var desiredPivot = size / 2f;
if (CardToControl.TryGetValue(card.CardId, out var control))
{
size = control.Size;
desiredScale = control.DesiredScale;
desiredPivot = control.DesiredPivotOffset;
}
visualWidth = size.X * desiredScale.X;
leftOffset = (1f - desiredScale.X) * desiredPivot.X;
topOffset = (1f - desiredScale.Y) * desiredPivot.Y;
bottomOffset = topOffset + size.Y * desiredScale.Y;
}
private void CalculateCardPositions(IReadOnlyList<Card> cards)
{
m_CardPositions.Clear();
var count = cards.Count;
if (count == 0)
return;
var cardHeight = CardSize.Y;
var cardIds = new Guid[count];
var visualWidths = new float[count];
var leftOffsets = new float[count];
var topOffsets = new float[count];
var bottomOffsets = new float[count];
var totalVisualWidth = 0f;
for (var index = 0; index < count; index++)
{
var card = cards[index];
cardIds[index] = card.CardId;
GetLayoutMetrics(card, out var visualWidth, out var leftOffset, out var topOffset, out var bottomOffset);
visualWidths[index] = visualWidth;
leftOffsets[index] = leftOffset;
topOffsets[index] = topOffset;
bottomOffsets[index] = bottomOffset;
totalVisualWidth += visualWidth;
}
var availableWidth = Size.X > 0 ? Size.X : CardSize.X;
var availableHeight = Size.Y > 0 ? Size.Y : CardSize.Y;
if (availableWidth <= 0)
availableWidth = totalVisualWidth + (count - 1) * CardSpacing;
if (availableHeight <= 0)
availableHeight = cardHeight;
if (WrapCards)
{
var rowSpacing = CardSpacing;
var rowTopOffsets = new List<float>();
var rowBottomOffsets = new List<float>();
var rowIndices = new int[count];
var rowWidth = 0f;
var rowIndex = 0;
var rowHasCards = false;
var rowTopOffset = 0f;
var rowBottomOffset = 0f;
for (var index = 0; index < count; index++)
{
var cardWidthForLayout = visualWidths[index];
var spacing = rowWidth > 0f ? CardSpacing : 0f;
if (rowWidth > 0f && rowWidth + spacing + cardWidthForLayout > availableWidth)
{
rowTopOffsets.Add(rowTopOffset);
rowBottomOffsets.Add(rowBottomOffset);
rowIndex += 1;
rowWidth = 0f;
rowHasCards = false;
spacing = 0f;
}
if (!rowHasCards)
{
rowTopOffset = topOffsets[index];
rowBottomOffset = bottomOffsets[index];
rowHasCards = true;
}
else
{
rowTopOffset = Mathf.Min(rowTopOffset, topOffsets[index]);
rowBottomOffset = Mathf.Max(rowBottomOffset, bottomOffsets[index]);
}
rowIndices[index] = rowIndex;
rowWidth += spacing + cardWidthForLayout;
}
if (rowHasCards)
{
rowTopOffsets.Add(rowTopOffset);
rowBottomOffsets.Add(rowBottomOffset);
}
var rowTops = new float[rowTopOffsets.Count];
if (rowTops.Length > 0)
{
rowTops[0] = 0f;
for (var index = 1; index < rowTopOffsets.Count; index++)
{
var previousBottom = rowTops[index - 1] + rowBottomOffsets[index - 1];
rowTops[index] = previousBottom + rowSpacing - rowTopOffsets[index];
}
}
var currentRow = 0;
var currentVisualLeft = 0f;
for (var index = 0; index < count; index++)
{
var cardRowIndex = rowIndices[index];
if (cardRowIndex != currentRow)
{
currentRow = cardRowIndex;
currentVisualLeft = 0f;
}
if (currentVisualLeft > 0f)
currentVisualLeft += CardSpacing;
var positionX = currentVisualLeft - leftOffsets[index];
var positionY = rowTops[cardRowIndex];
m_CardPositions[cardIds[index]] = new Vector2(positionX, positionY);
currentVisualLeft += visualWidths[index];
}
if (rowTopOffsets.Count > 0)
{
var totalHeight = rowTops[^1] + rowBottomOffsets[^1];
CustomMinimumSize = new(0, totalHeight);
}
}
else
{
var spacing = CardSpacing;
var widthWithBaseSpacing = totalVisualWidth + (count - 1) * CardSpacing;
if (count > 1)
{
if (widthWithBaseSpacing > availableWidth)
{
spacing = (availableWidth - totalVisualWidth) / (count - 1);
}
}
var startVisualLeft = (availableWidth - widthWithBaseSpacing) * 0.5f;
var y = (availableHeight - cardHeight) * 0.5f;
var currentVisualLeft = startVisualLeft;
for (var index = 0; index < count; index++)
{
var positionX = currentVisualLeft - leftOffsets[index];
m_CardPositions[cardIds[index]] = new Vector2(positionX, y);
currentVisualLeft += visualWidths[index] + spacing;
}
}
}
public void RunCard(Card card, Tween tween)
{
if (!CardToControl.Remove(card.CardId, out var control))
return;
Main.Instance.Music.Play(MusicManager.ESound.Card);
control.QueueFree();
OrderedCards.Remove(card);
LayoutCards();
}
public void ModifyCard(Card card, EModifierId modifier, Tween tween)
{
if (!CardToControl.TryGetValue(card.CardId, out var control))
{
Debug.WriteLine($"Failed to modify {card.Id}");
return;
}
var hasModifier = card.Modifiers.Any(m => m.Id == modifier && m.DebuffSources.Count == 0);
Debug.WriteLine($"Modify {card.Id}");
control.Configure(card, this);
if (hasModifier)
Main.Instance.Music.Play(modifier);
}
private CardControl CreateCardControl(Card card)
{
var node = CardScene.Instantiate<CardControl>();
node.Name = $"Card_{card.Id}";
node.SetAnchorsPreset(LayoutPreset.TopLeft);
node.Size = CardSize;
node.PivotOffset = CardSize / 2f;
node.CanDrag = CardsCanDrag;
node.CanSelect = CardsCanSelect;
node.Configure(card, this);
node.Connect(CardControl.SignalName.CardDroppedOn, new(this, nameof(OnCardDroppedOn)));
node.Connect(CardControl.SignalName.CardSelectionChanged, new(this, nameof(OnCardSelectionChanged)));
node.Connect(CardControl.SignalName.CardStateChanged, new(this, nameof(OnCardStateChanged)));
return node;
}
private void OnCardStateChanged(CardControl card)
{
m_LayoutDirty = true;
}
private void OnCardSelectionChanged(CardControl card, bool selected)
{
if (!m_SuppressSelectionValidation && CanChangeSelection != null && !CanChangeSelection(card, selected))
{
m_SuppressSelectionValidation = true;
card.Selected = !selected;
m_SuppressSelectionValidation = false;
return;
}
if (m_SuppressSelectionSignals)
return;
if (selected)
SelectionContext = this;
if (selected && !SupportsMultiSelect)
{
m_SuppressSelectionValidation = true;
foreach (var other in CardToControl.Values)
{
if (other != card && other.Selected)
other.Selected = false;
}
m_SuppressSelectionValidation = false;
}
EmitSignal(SignalName.SelectionChanged, CardToControl.Values.Where(c => c.Selected).ToArray());
}
private void OnCardDroppedOn(CardControl source, CardControl target, bool before)
{
if (source == target)
return;
var sourceRow = source.CardRow;
var targetRow = target.CardRow;
var fromCardId = sourceRow.CardToControl.FirstOrDefault(kv => kv.Value == source).Key;
var toCardId = targetRow.CardToControl.FirstOrDefault(kv => kv.Value == target).Key;
if (fromCardId == Guid.Empty || toCardId == Guid.Empty)
return;
var fromCard = sourceRow.OrderedCards.Single(c => c.CardId == fromCardId);
var toCard = OrderedCards.Single(c => c.CardId == toCardId);
var draggedCards = GetDraggedCards(sourceRow, fromCard);
if (draggedCards.Contains(toCard))
{
if (SelectedCards.Contains(toCard))
{
draggedCards = [fromCard];
}
else
{
return;
}
}
var targetCards = targetRow.OrderedCards;
var toIndex = targetCards.IndexOf(toCard);
if (toIndex == -1)
return;
var insertIndex = before ? toIndex : toIndex + 1;
MoveCards(sourceRow, targetRow, draggedCards, insertIndex);
}
public override bool _CanDropData(Vector2 atPosition, Variant data)
{
if (Disabled || !CardsCanDrag)
return false;
if (data.Obj is not CardControl sourceControl)
return false;
var sourceRow = sourceControl.CardRow;
var fromCardId = sourceRow.CardToControl.FirstOrDefault(kv => kv.Value == sourceControl).Key;
if (fromCardId == Guid.Empty)
return false;
var fromCard = sourceRow.OrderedCards.Single(c => c.CardId == fromCardId);
var draggedCards = GetDraggedCards(sourceRow, fromCard);
if (draggedCards.Count == 0)
return false;
if (sourceRow != this && OccupiedSpace + draggedCards.Select(c => c.OccupiedSpace).Sum() > MaxCards)
return false;
sourceControl.UpdateDragPreviewForDropTarget(null);
return true;
}
public override void _DropData(Vector2 atPosition, Variant data)
{
if (Disabled || !CardsCanDrag)
return;
if (data.Obj is not CardControl sourceControl)
return;
var sourceRow = sourceControl.CardRow;
var fromCardId = sourceRow?.CardToControl.FirstOrDefault(kv => kv.Value == sourceControl).Key;
if (fromCardId == Guid.Empty)
return;
var fromCard = sourceRow.OrderedCards.Single(c => c.CardId == fromCardId);
var draggedCards = GetDraggedCards(sourceRow, fromCard);
if (draggedCards.Count == 0)
return;
if (OccupiedSpace + draggedCards.Select(c => c.OccupiedSpace).Sum() > MaxCards)
return;
var insertIndex = GetDropIndex(atPosition);
MoveCards(sourceRow, this, draggedCards, insertIndex);
}
private List<Card> GetDraggedCards(CardRow sourceRow, Card fromCard)
{
if (fromCard == null)
return [];
if (!sourceRow.SupportsMultiDrag)
return [fromCard];
var selected = sourceRow.SelectedCardIndices.Select(i => sourceRow.OrderedCards[i]).ToList();
if (selected.Count > 1 && selected.Contains(fromCard))
{
var originalOrder = sourceRow.OrderedCards;
return selected.OrderBy(c => originalOrder.IndexOf(c)).ToList();
}
return [fromCard];
}
private void MoveCards(CardRow sourceRow, CardRow targetRow, List<Card> draggedCards, int rawInsertIndex)
{
if (draggedCards == null || draggedCards.Count == 0)
return;
var draggedSet = new HashSet<Card>(draggedCards);
if (sourceRow == targetRow)
{
var original = sourceRow.OrderedCards.ToList();
if (original.Count == 0)
return;
var insertIndex = Mathf.Clamp(rawInsertIndex, 0, original.Count);
var removedBefore = 0;
foreach (var card in draggedCards)
{
var idx = original.IndexOf(card);
if (idx >= 0 && idx < insertIndex)
removedBefore++;
}
var adjustedIndex = insertIndex - removedBefore;
var newOrder = original.Where(c => !draggedSet.Contains(c)).ToList();
adjustedIndex = Mathf.Clamp(adjustedIndex, 0, newOrder.Count);
newOrder.InsertRange(adjustedIndex, draggedCards);
sourceRow.Configure(newOrder, null, false, true);
sourceRow.EmitSignal(SignalName.OrderChanged);
}
else
{
var sourceList = sourceRow.OrderedCards.ToList();
var targetList = targetRow.OrderedCards.ToList();
foreach (var card in draggedCards)
{
sourceList.Remove(card);
targetList.Remove(card);
}
var insertIndex = Mathf.Clamp(rawInsertIndex, 0, targetList.Count);
targetList.InsertRange(insertIndex, draggedCards);
sourceRow.Configure(sourceList, null, false, true);
targetRow.Configure(targetList, null, false, true);
targetRow.EmitSignal(SignalName.OrderChanged);
}
}
private int GetDropIndex(Vector2 localPosition)
{
var count = OrderedCards.Count;
if (count == 0)
return 0;
var orderedControls = OrderedCards.Select(card => CardToControl.GetValueOrDefault(card.CardId)).Where(control => control != null).ToList();
if (orderedControls.Count == 0)
return 0;
var mouseX = localPosition.X;
if (mouseX <= orderedControls[0].Position.X)
return 0;
var last = orderedControls[^1];
if (mouseX >= last.Position.X + CardSize.X)
return orderedControls.Count;
for (var i = 0; i < orderedControls.Count; i++)
{
var control = orderedControls[i];
if (mouseX < control.Position.X)
return i;
}
return orderedControls.Count;
}
private void LayoutCards()
{
Debug.WriteLine("Layout");
var count = OrderedCards.Count;
if (count == 0)
return;
m_LastPosition = GlobalPosition;
m_LastSize = Size;
CalculateCardPositions(OrderedCards);
foreach (var card in OrderedCards)
{
if (!CardToControl.TryGetValue(card.CardId, out var control))
continue;
if (m_CardPositions.TryGetValue(card.CardId, out var targetPos))
control.SetDesiredPosition(targetPos);
}
}
private void CacheBackgroundPanels()
{
m_Backgrounds.Clear();
foreach (var child in GetChildren())
{
if (child is Control control && control.Name.ToString().StartsWith("Background"))
{
control.ZIndex = 0;
control.MouseFilter = MouseFilterEnum.Ignore;
m_Backgrounds.Add(control);
}
}
}
private void UpdateBackgroundVisibility()
{
if (m_Backgrounds.Count == 0)
return;
var clamped = Mathf.Clamp(BackgroundVariant, 0, m_Backgrounds.Count - 1);
for (var i = 0; i < m_Backgrounds.Count; i++)
m_Backgrounds[i].Visible = i == clamped;
}
public void ApplySelection(IReadOnlyCollection<Guid> selectedCardIds, bool suppressSignals)
{
m_SuppressSelectionValidation = true;
m_SuppressSelectionSignals = suppressSignals;
foreach (var kvp in CardToControl)
{
kvp.Value.Selected = selectedCardIds.Contains(kvp.Key);
}
m_SuppressSelectionSignals = false;
m_SuppressSelectionValidation = false;
}
public void ClearSelection()
{
ApplySelection([], true);
EmitSignal(SignalName.SelectionChanged, []);
}
public static List<Card> GetSortedCards(IEnumerable<Card> cards)
{
return cards.Order(Comparer<Card>.Create((a, b) =>
{
var cmp = a.Rarity.CompareTo(b.Rarity);
if (cmp == 0)
cmp = a.Id.CompareTo(b.Id);
if (cmp == 0)
cmp = a.Modifiers.Count.CompareTo(b.Modifiers.Count);
if (cmp == 0)
cmp = a.CardId.CompareTo(b.CardId);
return cmp;
})).ToList();
}
[Export]
public PackedScene CardScene { get; set; }
[Export]
public Vector2 CardSize { get; set; } = new(96, 96);
[Export]
public float CardSpacing { get; set; } = 8f;
[Export]
public float AnimationDuration { get; set; } = 0.15f;
[Export]
public bool CardsCanDrag { get; set; } = true;
[Export]
public bool CardsCanSelect { get; set; } = false;
[Export]
public bool SupportsMultiSelect { get; set; } = false;
[Export]
public bool SupportsMultiDrag { get; set; } = false;
[Export]
public int BackgroundVariant
{
get => m_BackgroundVariant;
set
{
if (m_BackgroundVariant == value)
return;
m_BackgroundVariant = value;
UpdateBackgroundVisibility();
}
}
public Func<CardControl, bool, bool> CanChangeSelection { get; set; }
public static bool IsCardPressActive => s_CardPressActive;
public static void NotifyCardPress(bool pressed)
{
s_CardPressActive = pressed;
}
public static CardRow SelectionContext
{
get => s_SelectionContext;
set
{
if (s_SelectionContext == value)
return;
if (s_SelectionContext != null)
{
foreach (var card in s_SelectionContext.CardToControl)
card.Value.Selected = false;
}
s_SelectionContext = value;
}
}
[Export]
public bool Disabled
{
get => m_Disabled;
set
{
if (m_Disabled == value)
return;
m_Disabled = value;
foreach (var card in CardToControl)
card.Value.Disabled = m_Disabled;
}
}
[Export]
public int MaxCards { get; set; } = int.MaxValue;
[Export]
public bool WrapCards { get; set; } = false;
public int OccupiedSpace => OrderedCards.Select(c => c.OccupiedSpace).Sum();
public IEnumerable<int> SelectedCardIndices =>
OrderedCards.Select((c, i) => (Card: CardToControl[c.CardId], Index: i)).Where(pair => pair.Card.Selected).Select(pair => pair.Index);
public IReadOnlyList<Guid> SelectedCardIds =>
OrderedCards.Where(card => CardToControl.TryGetValue(card.CardId, out var control) && control.Selected)
.Select(card => card.CardId)
.ToList();
public IReadOnlyList<Card> SelectedCards =>
OrderedCards.Where(card => CardToControl.TryGetValue(card.CardId, out var control) && control.Selected)
.ToList();
public List<Card> OrderedCards { get; } = [];
public Dictionary<Guid, CardControl> CardToControl => m_CardToControl;
[Signal]
public delegate void OrderChangedEventHandler();
[Signal]
public delegate void SelectionChangedEventHandler(CardControl[] selection);
private static CardRow s_SelectionContext;
private static bool s_CardPressActive;
private readonly List<Control> m_Backgrounds = [];
private readonly Dictionary<Guid, CardControl> m_CardToControl = new();
private readonly Dictionary<Guid, Vector2> m_CardPositions = [];
private int m_BackgroundVariant;
private bool m_Disabled;
private bool m_SuppressSelectionValidation;
private bool m_SuppressSelectionSignals;
private bool m_LayoutDirty;
private Vector2 m_LastPosition;
private Vector2 m_LastSize;
}