598 lines
18 KiB
C#
598 lines
18 KiB
C#
using Godot;
|
|
using RobotAndDonkey.Game.Cards;
|
|
using RobotAndDonkey.Game.Cards.Patches;
|
|
using RobotAndDonkey.Game.Modifiers;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using RobotAndDonkey.Game;
|
|
|
|
public partial class CardControl : Control
|
|
{
|
|
public override void _Ready()
|
|
{
|
|
m_ArtTextureRect = GetNode<TextureRect>("%ArtTextureRect");
|
|
m_ModifiersContainer = GetNode<HBoxContainer>("%ModifiersContainer");
|
|
m_BorderPanel = GetNode<Panel>("%Frame");
|
|
m_Background = GetNode<Panel>("%Background");
|
|
m_Label = GetNode<Label>("%Label");
|
|
|
|
MouseEntered += OnMouseEntered;
|
|
MouseExited += OnMouseExited;
|
|
|
|
PivotOffset = Size / 2;
|
|
Scale = new(1, 1);
|
|
Modulate = new(1, 1, 1);
|
|
m_DesiredPosition = Position;
|
|
UpdateState();
|
|
SnapToDesired();
|
|
|
|
if (ModifierIconScene == null)
|
|
GD.PushWarning($"{nameof(CardControl)} on node '{Name}' has no ModifierIconScene assigned. No modifier icons will be shown.");
|
|
|
|
if (m_Card == null)
|
|
{
|
|
m_Card = new DetoxiumPrime();
|
|
m_Card.AddModifier(new UnreliableCardModifier(EModifierDuration.Permanent), []);
|
|
m_Card.AddModifier(new EffectiveModifier(EModifierDuration.Permanent), []);
|
|
m_Card.AddModifier(new RaceConditionModifier(EModifierDuration.Permanent), []);
|
|
m_Card.AddModifier(new OptimizedModifier(EModifierDuration.Permanent), []);
|
|
}
|
|
Refresh();
|
|
}
|
|
|
|
public override void _Process(double delta)
|
|
{
|
|
UpdateAnimatedState(delta);
|
|
}
|
|
|
|
private void OnMouseEntered()
|
|
{
|
|
if (m_Dragging || GetViewport().GuiIsDragging())
|
|
return;
|
|
|
|
if (!CanDrag && !CanSelect || Disabled)
|
|
return;
|
|
|
|
if (ShouldHoverSelect())
|
|
Selected = true;
|
|
|
|
m_Hover = true;
|
|
ZIndex = 1;
|
|
UpdateState();
|
|
}
|
|
|
|
private bool ShouldHoverSelect()
|
|
{
|
|
if (!CanSelect || Disabled || m_CardRow == null)
|
|
return false;
|
|
|
|
if (!m_CardRow.SupportsMultiSelect)
|
|
return false;
|
|
|
|
if (!Input.IsMouseButtonPressed(MouseButton.Left))
|
|
return false;
|
|
|
|
if (CardRow.IsCardPressActive)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnMouseExited()
|
|
{
|
|
m_PressedLeftDown = false;
|
|
if (m_Dragging)
|
|
return;
|
|
|
|
if (!CanDrag && !CanSelect || Disabled)
|
|
return;
|
|
|
|
m_DropBefore = null;
|
|
m_Hover = false;
|
|
ZIndex = 0;
|
|
|
|
UpdateState();
|
|
}
|
|
|
|
private void UpdateState()
|
|
{
|
|
Vector2 newPivot = Size / 2;
|
|
Vector2 newScale = new Vector2(1, 1);
|
|
Color newModulate = new Color(1, 1, 1, 1);
|
|
if (Selected)
|
|
{
|
|
newPivot.Y = Size.Y;
|
|
newScale = new(1.25f, 1.25f);
|
|
}
|
|
|
|
if (m_Hover)
|
|
{
|
|
newScale = new(1.25f, 1.25f);
|
|
newModulate = new(1.25f, 1.25f, 1.25f);
|
|
}
|
|
|
|
if (m_DropBefore == false)
|
|
{
|
|
newPivot.X = Size.X;
|
|
newScale = new(1.25f, 1.25f);
|
|
newModulate.A = 0.5f;
|
|
}
|
|
else if (m_DropBefore == true)
|
|
{
|
|
newPivot.X = 0;
|
|
newScale = new(1.25f, 1.25f);
|
|
newModulate.A = 0.5f;
|
|
}
|
|
if (m_Dragging)
|
|
{
|
|
newModulate.A = 0.25f;
|
|
}
|
|
|
|
if (m_DesiredModulate == newModulate && m_DesiredScale == newScale && m_DesiredPivotOffset == newPivot)
|
|
return;
|
|
|
|
m_DesiredPivotOffset = newPivot;
|
|
m_DesiredScale = newScale;
|
|
m_DesiredModulate = newModulate;
|
|
EmitSignal(SignalName.CardStateChanged, this);
|
|
}
|
|
|
|
public void Configure(Card card, CardRow cardRow)
|
|
{
|
|
m_Card = card;
|
|
m_CardRow = cardRow;
|
|
Refresh();
|
|
UpdateState();
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
RefreshRarity();
|
|
RefreshArtTexture();
|
|
SetDescription(m_Card.Name, m_Card.ToolTip);
|
|
SetModifiers(m_Card.Modifiers);
|
|
}
|
|
|
|
private void RefreshArtTexture()
|
|
{
|
|
SetArtTexture(GD.Load<Texture2D>(m_Card.Id switch
|
|
{
|
|
ECard.Bitflip => "res://images/bitflip.png",
|
|
ECard.ShortCircuit => "res://images/short-circuit.png",
|
|
ECard.Slipstream => "res://images/slipstream.png",
|
|
ECard.Latency => "res://images/latency.png",
|
|
ECard.Rain => "res://images/rain.png",
|
|
ECard.Drought => "res://images/drought.png",
|
|
ECard.Pest => "res://images/pest.png",
|
|
ECard.Gravity => "res://images/gravity.png",
|
|
ECard.HeatWave => "res://images/heat-wave.png",
|
|
ECard.SolarFlare => "res://images/solar-flare.png",
|
|
ECard.LightningStorm => "res://images/lightning-storm.png",
|
|
ECard.MeteorStorm => "res://images/meteor-storm.png",
|
|
ECard.WindStorm => "res://images/wind-storm.png",
|
|
ECard.Move => "res://images/move.png",
|
|
ECard.TurnLeft => "res://images/turn-left.png",
|
|
ECard.TurnRight => "res://images/turn-right.png",
|
|
ECard.Interact => "res://images/interact.png",
|
|
ECard.NoOp => "res://images/no-op.png",
|
|
ECard.Potentiate => "res://images/potentiate.png",
|
|
ECard.Optimize => "res://images/optimize.png",
|
|
ECard.Streamline => "res://images/streamline.png",
|
|
ECard.Persist => "res://images/persist.png",
|
|
ECard.Remember => "res://images/remember.png",
|
|
ECard.Reason => "res://images/reason.png",
|
|
ECard.DetoxiumPrime => "res://images/detoxium-prime.png",
|
|
ECard.FlyingDisk => "res://images/flying-disk.png",
|
|
ECard.AluminumHat => "res://images/aluminum-hat.png",
|
|
ECard.EMField => "res://images/em-field.png",
|
|
ECard.AtomicClock => "res://images/atomic-clock.png",
|
|
ECard.Jump => "res://images/jump.png",
|
|
ECard.Repeat => "res://images/repeat.png",
|
|
ECard.Rest => "res://images/rest.png",
|
|
ECard.Stabilize => "res://images/stabilize.png",
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
}));
|
|
}
|
|
|
|
private void RefreshRarity()
|
|
{
|
|
var color = m_Card.Rarity switch
|
|
{
|
|
ERarity.Common => new(1, 1, 1),
|
|
ERarity.Magic => new(0.3f, 1, 0.3f),
|
|
ERarity.Uncommon => new(0.4f, 0.6f, 1.5f),
|
|
ERarity.Rare => new(0.8f, 0.3f, 1),
|
|
ERarity.Legendary => new Color(1, 0.5f, 0.2f),
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
SetRarity(color);
|
|
}
|
|
|
|
public void SetRarity(Color color)
|
|
{
|
|
if (m_BorderPanel == null || m_Background == null)
|
|
return;
|
|
|
|
m_BorderPanel.Modulate = color;
|
|
m_Background.Modulate = color;
|
|
}
|
|
|
|
public void SetArtTexture(Texture2D texture)
|
|
{
|
|
if (m_ArtTextureRect == null)
|
|
return;
|
|
|
|
m_ArtTextureRect.Texture = texture;
|
|
}
|
|
|
|
public void SetDescription(string displayName, string description)
|
|
{
|
|
if (m_Label != null)
|
|
m_Label.Text = displayName;
|
|
|
|
var header = string.IsNullOrEmpty(displayName) ? string.Empty : displayName.Trim();
|
|
var body = string.IsNullOrEmpty(description) ? string.Empty : description.Trim();
|
|
|
|
if (!string.IsNullOrEmpty(header) && !string.IsNullOrEmpty(body))
|
|
TooltipText = $"{header}\n{body}";
|
|
else if (!string.IsNullOrEmpty(header))
|
|
TooltipText = header;
|
|
else
|
|
TooltipText = body;
|
|
}
|
|
|
|
public void SetModifiers(IReadOnlyList<Modifier> modifiers)
|
|
{
|
|
if (m_ModifiersContainer == null)
|
|
return;
|
|
|
|
foreach (var child in m_ModifiersContainer.GetChildren())
|
|
{
|
|
child.QueueFree();
|
|
}
|
|
|
|
foreach (var modifier in modifiers)
|
|
{
|
|
if (modifier.DebuffSources.Count > 0)
|
|
continue;
|
|
|
|
var icon = ModifierIconScene.Instantiate<ModifierIcon>();
|
|
icon.Configure(modifier);
|
|
m_ModifiersContainer.AddChild(icon);
|
|
}
|
|
}
|
|
|
|
public void AnimateCard(Tween tween)
|
|
{
|
|
}
|
|
|
|
public void SetDesiredPosition(Vector2 desiredPosition)
|
|
{
|
|
m_DesiredPosition = desiredPosition;
|
|
}
|
|
|
|
public void SnapToDesired()
|
|
{
|
|
Position = m_DesiredPosition;
|
|
PivotOffset = m_DesiredPivotOffset;
|
|
Scale = m_DesiredScale;
|
|
Modulate = m_DesiredModulate;
|
|
}
|
|
|
|
private void UpdateAnimatedState(double delta)
|
|
{
|
|
var weight = GetBlendWeight(delta);
|
|
if (weight >= 1f)
|
|
{
|
|
SnapToDesired();
|
|
return;
|
|
}
|
|
|
|
Position = Position.Lerp(m_DesiredPosition, weight);
|
|
PivotOffset = PivotOffset.Lerp(m_DesiredPivotOffset, weight);
|
|
Scale = Scale.Lerp(m_DesiredScale, weight);
|
|
Modulate = Modulate.Lerp(m_DesiredModulate, weight);
|
|
}
|
|
|
|
private float GetBlendWeight(double delta)
|
|
{
|
|
var duration = GetAnimationDuration() * 0.05f;
|
|
if (duration <= 0f)
|
|
return 1f;
|
|
|
|
var weight = 1f - Mathf.Exp(-(float)delta / duration);
|
|
return Mathf.Clamp(weight, 0f, 1f);
|
|
}
|
|
|
|
private float GetAnimationDuration()
|
|
{
|
|
if (m_CardRow != null)
|
|
return m_CardRow.AnimationDuration;
|
|
|
|
return 0.15f;
|
|
}
|
|
|
|
public override void _GuiInput(InputEvent @event)
|
|
{
|
|
if (@event is not InputEventMouseButton mouseButton || mouseButton.ButtonIndex != MouseButton.Left)
|
|
return;
|
|
|
|
AcceptEvent();
|
|
if (Disabled)
|
|
return;
|
|
|
|
if (mouseButton.Pressed)
|
|
{
|
|
m_PressedLeftDown = true;
|
|
CardRow.NotifyCardPress(true);
|
|
}
|
|
else
|
|
{
|
|
if (m_PressedLeftDown && CanSelect)
|
|
Selected = !Selected;
|
|
|
|
m_PressedLeftDown = false;
|
|
CardRow.NotifyCardPress(false);
|
|
}
|
|
}
|
|
|
|
public override Variant _GetDragData(Vector2 atPosition)
|
|
{
|
|
if (!CanDrag || Disabled)
|
|
return default;
|
|
|
|
m_Dragging = true;
|
|
UpdateState();
|
|
UpdateDragPreviewForDropTarget(null);
|
|
|
|
return this;
|
|
}
|
|
|
|
public override void _Notification(int what)
|
|
{
|
|
switch ((long)what)
|
|
{
|
|
case NotificationDragBegin:
|
|
m_DropBefore = null;
|
|
UpdateState();
|
|
break;
|
|
case NotificationDragEnd:
|
|
m_Dragging = false;
|
|
m_Hover = false;
|
|
m_DropBefore = null;
|
|
m_DragPreviewRoot = null;
|
|
m_DragPreviewCards = null;
|
|
UpdateState();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override bool _CanDropData(Vector2 atPosition, Variant data)
|
|
{
|
|
if (!CanDrag || Disabled)
|
|
return false;
|
|
|
|
var dataObject = data.AsGodotObject();
|
|
if (dataObject is not CardControl sourceControl || sourceControl == this)
|
|
return false;
|
|
|
|
if (GetParent<CardRow>() is { } cardRow)
|
|
{
|
|
if (!cardRow._CanDropData(atPosition, data))
|
|
return false;
|
|
}
|
|
|
|
sourceControl.UpdateDragPreviewForDropTarget(this);
|
|
var size = Size.X / 2;
|
|
var before = atPosition.X < size;
|
|
if (before != m_DropBefore)
|
|
{
|
|
GD.Print($"Can drop before={before} {m_Card.Id}");
|
|
m_DropBefore = before;
|
|
UpdateState();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void UpdateDragPreviewForDropTarget(CardControl targetControl)
|
|
{
|
|
var draggedCards = GetDraggedCardsForPreview();
|
|
if (ShouldForceSinglePreview(targetControl, draggedCards))
|
|
SetDragPreviewCards([this]);
|
|
else
|
|
SetDragPreviewCards(draggedCards);
|
|
}
|
|
|
|
private IReadOnlyList<CardControl> GetDraggedCardsForPreview()
|
|
{
|
|
if (m_CardRow == null || !m_CardRow.SupportsMultiDrag)
|
|
return [this];
|
|
|
|
var selectedCards = m_CardRow.SelectedCards;
|
|
if (selectedCards.Count <= 1)
|
|
return [this];
|
|
|
|
foreach (var selectedCard in selectedCards)
|
|
{
|
|
if (selectedCard.CardId == Card.CardId)
|
|
return selectedCards.Select(c => m_CardRow.CardToControl[c.CardId]).ToList();
|
|
}
|
|
|
|
return [this];
|
|
}
|
|
|
|
private bool ShouldForceSinglePreview(CardControl targetControl, IReadOnlyList<CardControl> draggedCards)
|
|
{
|
|
if (targetControl == null)
|
|
return false;
|
|
|
|
if (draggedCards.Count <= 1)
|
|
return false;
|
|
|
|
if (!targetControl.Selected)
|
|
return false;
|
|
|
|
foreach (var draggedCard in draggedCards)
|
|
{
|
|
if (draggedCard == targetControl)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SetDragPreviewCards(IReadOnlyList<CardControl> cards)
|
|
{
|
|
if (m_DragPreviewCards != null && m_DragPreviewCards.SequenceEqual(cards))
|
|
return;
|
|
|
|
m_DragPreviewCards = cards.ToList();
|
|
|
|
foreach (var card in m_CardRow.CardToControl.Values)
|
|
{
|
|
card.m_Dragging = false;
|
|
card.UpdateState();
|
|
}
|
|
|
|
if (m_DragPreviewRoot != null)
|
|
{
|
|
foreach (var child in m_DragPreviewRoot.GetChildren())
|
|
child.QueueFree();
|
|
}
|
|
|
|
if (cards.Count == 0)
|
|
return;
|
|
|
|
if (cards.Count == 1)
|
|
{
|
|
m_DragPreviewRoot?.QueueFree();
|
|
m_DragPreviewRoot = null;
|
|
var cardControl = cards[0];
|
|
var previewCard = (CardControl)cardControl.Duplicate();
|
|
previewCard.SetAnchorsPreset(LayoutPreset.TopLeft);
|
|
previewCard.Configure(cardControl.Card, null);
|
|
previewCard.Selected = cardControl.Selected;
|
|
previewCard.UpdateState();
|
|
previewCard.SetDesiredPosition(previewCard.Position);
|
|
previewCard.SnapToDesired();
|
|
SetDragPreview(previewCard);
|
|
return;
|
|
}
|
|
|
|
if (m_DragPreviewRoot == null)
|
|
{
|
|
m_DragPreviewRoot = new();
|
|
m_DragPreviewRoot.MouseFilter = MouseFilterEnum.Ignore;
|
|
}
|
|
|
|
var spacing = m_CardRow?.CardSpacing ?? 0f;
|
|
var offset = Mathf.Max(6f, spacing * 2f);
|
|
var offsetVector = new Vector2(offset, offset);
|
|
|
|
for (var index = 0; index < cards.Count; index++)
|
|
{
|
|
var cardControl = cards[index];
|
|
cardControl.m_Dragging = true;
|
|
cardControl.UpdateState();
|
|
var previewCard = (CardControl)cardControl.Duplicate();
|
|
previewCard.SetAnchorsPreset(LayoutPreset.TopLeft);
|
|
previewCard.Configure(cardControl.Card, null);
|
|
previewCard.Position = new(offsetVector.X * index, offsetVector.Y * index);
|
|
previewCard.Size = Size;
|
|
previewCard.UpdateState();
|
|
previewCard.SetDesiredPosition(previewCard.Position);
|
|
previewCard.SnapToDesired();
|
|
previewCard.ZIndex = index + 1;
|
|
m_DragPreviewRoot.AddChild(previewCard);
|
|
}
|
|
|
|
m_DragPreviewRoot.Size = Size + offsetVector * Math.Max(0, cards.Count - 1);
|
|
SetDragPreview(m_DragPreviewRoot);
|
|
}
|
|
|
|
public override void _DropData(Vector2 atPosition, Variant data)
|
|
{
|
|
if (!CanDrag)
|
|
return;
|
|
|
|
var obj = data.AsGodotObject();
|
|
if (obj is CardControl source)
|
|
{
|
|
var size = Size.X / 2;
|
|
var before = atPosition.X < size;
|
|
EmitSignal(SignalName.CardDroppedOn, source, this, before);
|
|
}
|
|
}
|
|
|
|
[Export]
|
|
public bool CanDrag { get; set; }
|
|
|
|
[Export]
|
|
public bool CanSelect { get; set; }
|
|
|
|
[Export]
|
|
public bool Disabled { get; set; }
|
|
|
|
[Export]
|
|
public bool Selected
|
|
{
|
|
get => m_Selected;
|
|
set
|
|
{
|
|
if (m_Selected == value)
|
|
return;
|
|
|
|
m_Selected = value;
|
|
EmitSignal(SignalName.CardSelectionChanged, this, m_Selected);
|
|
UpdateState();
|
|
|
|
if (m_Selected && m_CardRow != null)
|
|
{
|
|
Tooltip.Instance.Describe(Card);
|
|
CardRow.SelectionContext = m_CardRow;
|
|
}
|
|
}
|
|
}
|
|
|
|
public CardRow CardRow => m_CardRow;
|
|
|
|
public Card Card => m_Card;
|
|
|
|
public Vector2 DesiredScale => m_DesiredScale;
|
|
|
|
public Vector2 DesiredPivotOffset => m_DesiredPivotOffset;
|
|
|
|
[Signal]
|
|
public delegate void CardDroppedOnEventHandler(CardControl source, CardControl target, bool before);
|
|
|
|
[Signal]
|
|
public delegate void CardSelectionChangedEventHandler(CardControl card, bool selected);
|
|
|
|
[Signal]
|
|
public delegate void CardStateChangedEventHandler(CardControl card);
|
|
|
|
[Export]
|
|
public PackedScene ModifierIconScene { get; set; }
|
|
|
|
private TextureRect m_ArtTextureRect;
|
|
private Panel m_Background;
|
|
private Panel m_BorderPanel;
|
|
private HBoxContainer m_ModifiersContainer;
|
|
private Card m_Card;
|
|
private bool m_PressedLeftDown;
|
|
private bool m_Dragging;
|
|
private bool m_Selected;
|
|
private bool m_Hover;
|
|
private bool? m_DropBefore;
|
|
private CardRow m_CardRow;
|
|
private Label m_Label;
|
|
private HBoxContainer m_DragPreviewRoot;
|
|
private IReadOnlyList<CardControl> m_DragPreviewCards;
|
|
private Vector2 m_DesiredPosition;
|
|
private Vector2 m_DesiredPivotOffset;
|
|
private Vector2 m_DesiredScale;
|
|
private Color m_DesiredModulate;
|
|
}
|