Line endings: LF everywhere
.gitattributes forces LF (CRLF only for .bat/.cmd), core.autocrlf is off; files that differed only by CRLF are normalized.
This commit is contained in:
1 parent
65e632a66f
commit
968b8d6fe1
20 files changed
+1740
-1710
No files matched your search
+267
-267
@@ -1,269 +1,269 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.Core.Scenes;
|
||||
using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.Core.Scenes;
|
||||
using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.View;
|
||||
namespace Cthangover.CardBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Container panel that manages a list of <see cref="CharacterCardNode"/> instances for one side
|
||||
/// of the battle (player or enemy). Handles card creation, multi-row layout when enemy count exceeds
|
||||
/// <c>MAX_CARDS_ROW</c> (10 per row), smooth animated repositioning, health-triggered death,
|
||||
/// and the <see cref="CardDeathAnimation"/> dissolve sequence.
|
||||
/// Two instances are created by <see cref="CardBattleCore.Init"/>: a left-aligned player panel
|
||||
/// and a right-aligned enemy panel.
|
||||
/// </summary>
|
||||
public partial class BattleCardPanel : ModWidget
|
||||
{
|
||||
private const int MAX_CARDS_ROW = 10;
|
||||
private const float BASE_CARD_HEIGHT = 280f;
|
||||
|
||||
[Export] private Control content;
|
||||
/// <summary>
|
||||
/// Controls card alignment within the panel. <c>"Left"</c> for the player side,
|
||||
/// <c>"Right"</c> for the enemy side. Affects <see cref="CalcTargetPosition"/>.
|
||||
/// </summary>
|
||||
[Export] public string AlignType { get; set; } = "Left";
|
||||
[Export] private float cellOffset = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// All character cards currently managed by this panel. Populated by <see cref="Init"/>
|
||||
/// and modified by <see cref="Dead"/> (removal) and <see cref="ClearAll"/> (full clear).
|
||||
/// </summary>
|
||||
public List<CharacterCardNode> CardList { get; } = new();
|
||||
private Dictionary<CharacterCardNode, Tween> activeTweens = new();
|
||||
|
||||
/// <summary>
|
||||
/// When <c>true</c>, cards are laid out in multiple rows (up to 3) instead of a single row.
|
||||
/// Set by <see cref="CardBattleCore.Start"/> when enemy count exceeds 6.
|
||||
/// </summary>
|
||||
public bool UseRows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a card's death animation completes. <see cref="CardBattleCore"/> subscribes
|
||||
/// to record the defeated character in <see cref="BattleSceneContext"/>.
|
||||
/// </summary>
|
||||
public event System.Action<CharacterCardNode> OnCardDead;
|
||||
|
||||
/// <summary>Builds the card grid container rows for one battle side.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
content ??= GetNodeOrNull<Control>("Content");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current card list and creates new <see cref="CharacterCardNode"/> instances
|
||||
/// for each <paramref name="cards"/> entry. Sets player/enemy team colors and subscribes
|
||||
/// the health-change handler. Returns the created card list.
|
||||
/// Called by <see cref="CardBattleCore.Start"/> with the full character roster for each side.
|
||||
/// </summary>
|
||||
public List<CharacterCardNode> Init(IEnumerable<Character> cards, bool isPlayer, float cardScale)
|
||||
{
|
||||
CardList.Clear();
|
||||
activeTweens.Clear();
|
||||
|
||||
int idx = 0;
|
||||
foreach (var card in cards)
|
||||
{
|
||||
CardList.Add(Create(card, isPlayer, cardScale));
|
||||
idx++;
|
||||
}
|
||||
Redraw(animate: false);
|
||||
|
||||
return CardList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a single <see cref="CharacterCardNode"/>, adds it to the content panel,
|
||||
/// sets its scale, initializes it with card data, applies team colors, and hooks
|
||||
/// the health-change event. Called by <see cref="Init"/> for each character.
|
||||
/// </summary>
|
||||
public CharacterCardNode Create(Character card, bool isPlayer, float cardScale)
|
||||
{
|
||||
var behaviour = (CharacterCardNode)TscnScenes.LoadAndBuild("scenes/character_card.tscn");
|
||||
behaviour.EnsureConstructed();
|
||||
|
||||
if (content != null)
|
||||
content.AddChild(behaviour);
|
||||
else
|
||||
AddChild(behaviour);
|
||||
|
||||
behaviour.Scale = new Vector2(cardScale, cardScale);
|
||||
|
||||
behaviour.Init(card);
|
||||
behaviour.IsPlayer = isPlayer;
|
||||
behaviour.SetTeamColors();
|
||||
|
||||
if (card != null)
|
||||
card.Attributes.Health.OnChange += CheckHealthAttribute;
|
||||
|
||||
return behaviour;
|
||||
}
|
||||
|
||||
private void CheckHealthAttribute(float value, float basevalue)
|
||||
{
|
||||
if (value > 0)
|
||||
return;
|
||||
|
||||
foreach (var cardItem in CardList.ToList())
|
||||
{
|
||||
if (cardItem.Card.Attributes.Health.Value <= 0)
|
||||
Dead(cardItem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all card nodes from the scene tree, clears the card list, and triggers a redraw.
|
||||
/// Called by <see cref="CardBattleCore.OnBattleCleared"/> when the battle scene is cleaned up.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var card in CardList.ToList())
|
||||
{
|
||||
if (GodotObject.IsInstanceValid(card))
|
||||
card.QueueFree();
|
||||
}
|
||||
CardList.Clear();
|
||||
activeTweens.Clear();
|
||||
Redraw(animate: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates and applies positions for all cards in the list. When <paramref name="animate"/>
|
||||
/// is <c>true</c>, cards tween to their new positions with a cubic ease-out; otherwise they
|
||||
/// snap instantly. Called after cards are added, removed, or when a selection/unselection
|
||||
/// changes the sort order.
|
||||
/// </summary>
|
||||
public void Redraw(bool animate = true)
|
||||
{
|
||||
for (int i = 0; i < CardList.Count; i++)
|
||||
{
|
||||
var card = CardList[i];
|
||||
if (card == null)
|
||||
continue;
|
||||
|
||||
var (row, col) = GetRowCol(i);
|
||||
SetCardPosition(card, i, row, col, animate);
|
||||
}
|
||||
}
|
||||
|
||||
private (int row, int col) GetRowCol(int index)
|
||||
{
|
||||
if (!UseRows || index < MAX_CARDS_ROW)
|
||||
return (0, index);
|
||||
|
||||
if (index < MAX_CARDS_ROW * 2)
|
||||
return (1, index - MAX_CARDS_ROW);
|
||||
|
||||
return (2, index - MAX_CARDS_ROW * 2);
|
||||
}
|
||||
|
||||
private int CalcBaseZIndex(int row, int col)
|
||||
{
|
||||
return row * 20 + col;
|
||||
}
|
||||
|
||||
private Vector2 CalcTargetPosition(CharacterCardNode card, int row, int col)
|
||||
{
|
||||
var panelSize = Size;
|
||||
float effectiveWidth = card.Size.X * card.Scale.X;
|
||||
float effectiveHeight = card.Size.Y * card.Scale.Y;
|
||||
|
||||
float y = row * (effectiveHeight + cellOffset);
|
||||
|
||||
if (AlignType == "Left")
|
||||
return new Vector2((cellOffset + effectiveWidth) * col, y);
|
||||
else
|
||||
return new Vector2(panelSize.X - (cellOffset + effectiveWidth) * (col + 1), y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates how many rows are needed to display <paramref name="enemyCount"/> enemies
|
||||
/// when <see cref="UseRows"/> is enabled. Returns 1, 2, or 3.
|
||||
/// Called by <see cref="CardBattleCore.CalculateCardScale"/> to determine maximum card scale.
|
||||
/// </summary>
|
||||
public int GetCurrentRowCount(int enemyCount)
|
||||
{
|
||||
if (!UseRows)
|
||||
return 1;
|
||||
|
||||
if (enemyCount <= MAX_CARDS_ROW)
|
||||
return 1;
|
||||
if (enemyCount <= MAX_CARDS_ROW * 2)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum cards per row constant (<c>MAX_CARDS_ROW = 10</c>).
|
||||
/// Used by <see cref="CardBattleCore.CalculateCardScale"/> for scale calculations.
|
||||
/// </summary>
|
||||
public int GetMaxCardsPerRow()
|
||||
{
|
||||
return MAX_CARDS_ROW;
|
||||
}
|
||||
|
||||
private void SetCardPosition(CharacterCardNode card, int index, int row, int col, bool animate)
|
||||
{
|
||||
if (card == null)
|
||||
return;
|
||||
|
||||
var targetPos = CalcTargetPosition(card, row, col);
|
||||
|
||||
if (activeTweens.TryGetValue(card, out var prevTween) && prevTween != null && prevTween.IsValid())
|
||||
prevTween.Kill();
|
||||
|
||||
if (animate)
|
||||
{
|
||||
var tween = CreateTween();
|
||||
tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
tween.TweenProperty(card, "position", targetPos, 0.3f);
|
||||
activeTweens[card] = tween;
|
||||
}
|
||||
else
|
||||
{
|
||||
card.Position = targetPos;
|
||||
activeTweens.Remove(card);
|
||||
}
|
||||
|
||||
var baseZ = CalcBaseZIndex(row, col);
|
||||
card.SetBaseZIndex(baseZ);
|
||||
|
||||
if (content != null)
|
||||
content.MoveChild(card, index);
|
||||
else
|
||||
MoveChild(card, index);
|
||||
card.UpdateInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the card as dead and starts the <see cref="CardDeathAnimation"/> dissolve sequence.
|
||||
/// Once the animation completes, the card is removed from <see cref="CardList"/>,
|
||||
/// <see cref="OnCardDead"/> is fired, and the remaining cards are redrawn.
|
||||
/// Triggered by the health-change handler when a character's health reaches zero.
|
||||
/// </summary>
|
||||
public void Dead(CharacterCardNode characterCardNode)
|
||||
{
|
||||
if (characterCardNode == null || characterCardNode.IsDead)
|
||||
return;
|
||||
|
||||
characterCardNode.IsDead = true;
|
||||
var animator = new Cthangover.CardBattle.UI.CardDeathAnimation();
|
||||
characterCardNode.AddChild(animator);
|
||||
animator.StartAnimation(characterCardNode, () =>
|
||||
{
|
||||
OnCardDead?.Invoke(characterCardNode);
|
||||
CardList.Remove(characterCardNode);
|
||||
activeTweens.Remove(characterCardNode);
|
||||
Redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Cthangover.CardBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Container panel that manages a list of <see cref="CharacterCardNode"/> instances for one side
|
||||
/// of the battle (player or enemy). Handles card creation, multi-row layout when enemy count exceeds
|
||||
/// <c>MAX_CARDS_ROW</c> (10 per row), smooth animated repositioning, health-triggered death,
|
||||
/// and the <see cref="CardDeathAnimation"/> dissolve sequence.
|
||||
/// Two instances are created by <see cref="CardBattleCore.Init"/>: a left-aligned player panel
|
||||
/// and a right-aligned enemy panel.
|
||||
/// </summary>
|
||||
public partial class BattleCardPanel : ModWidget
|
||||
{
|
||||
private const int MAX_CARDS_ROW = 10;
|
||||
private const float BASE_CARD_HEIGHT = 280f;
|
||||
|
||||
[Export] private Control content;
|
||||
/// <summary>
|
||||
/// Controls card alignment within the panel. <c>"Left"</c> for the player side,
|
||||
/// <c>"Right"</c> for the enemy side. Affects <see cref="CalcTargetPosition"/>.
|
||||
/// </summary>
|
||||
[Export] public string AlignType { get; set; } = "Left";
|
||||
[Export] private float cellOffset = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// All character cards currently managed by this panel. Populated by <see cref="Init"/>
|
||||
/// and modified by <see cref="Dead"/> (removal) and <see cref="ClearAll"/> (full clear).
|
||||
/// </summary>
|
||||
public List<CharacterCardNode> CardList { get; } = new();
|
||||
private Dictionary<CharacterCardNode, Tween> activeTweens = new();
|
||||
|
||||
/// <summary>
|
||||
/// When <c>true</c>, cards are laid out in multiple rows (up to 3) instead of a single row.
|
||||
/// Set by <see cref="CardBattleCore.Start"/> when enemy count exceeds 6.
|
||||
/// </summary>
|
||||
public bool UseRows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a card's death animation completes. <see cref="CardBattleCore"/> subscribes
|
||||
/// to record the defeated character in <see cref="BattleSceneContext"/>.
|
||||
/// </summary>
|
||||
public event System.Action<CharacterCardNode> OnCardDead;
|
||||
|
||||
/// <summary>Builds the card grid container rows for one battle side.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
content ??= GetNodeOrNull<Control>("Content");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current card list and creates new <see cref="CharacterCardNode"/> instances
|
||||
/// for each <paramref name="cards"/> entry. Sets player/enemy team colors and subscribes
|
||||
/// the health-change handler. Returns the created card list.
|
||||
/// Called by <see cref="CardBattleCore.Start"/> with the full character roster for each side.
|
||||
/// </summary>
|
||||
public List<CharacterCardNode> Init(IEnumerable<Character> cards, bool isPlayer, float cardScale)
|
||||
{
|
||||
CardList.Clear();
|
||||
activeTweens.Clear();
|
||||
|
||||
int idx = 0;
|
||||
foreach (var card in cards)
|
||||
{
|
||||
CardList.Add(Create(card, isPlayer, cardScale));
|
||||
idx++;
|
||||
}
|
||||
Redraw(animate: false);
|
||||
|
||||
return CardList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a single <see cref="CharacterCardNode"/>, adds it to the content panel,
|
||||
/// sets its scale, initializes it with card data, applies team colors, and hooks
|
||||
/// the health-change event. Called by <see cref="Init"/> for each character.
|
||||
/// </summary>
|
||||
public CharacterCardNode Create(Character card, bool isPlayer, float cardScale)
|
||||
{
|
||||
var behaviour = (CharacterCardNode)TscnScenes.LoadAndBuild("scenes/character_card.tscn");
|
||||
behaviour.EnsureConstructed();
|
||||
|
||||
if (content != null)
|
||||
content.AddChild(behaviour);
|
||||
else
|
||||
AddChild(behaviour);
|
||||
|
||||
behaviour.Scale = new Vector2(cardScale, cardScale);
|
||||
|
||||
behaviour.Init(card);
|
||||
behaviour.IsPlayer = isPlayer;
|
||||
behaviour.SetTeamColors();
|
||||
|
||||
if (card != null)
|
||||
card.Attributes.Health.OnChange += CheckHealthAttribute;
|
||||
|
||||
return behaviour;
|
||||
}
|
||||
|
||||
private void CheckHealthAttribute(float value, float basevalue)
|
||||
{
|
||||
if (value > 0)
|
||||
return;
|
||||
|
||||
foreach (var cardItem in CardList.ToList())
|
||||
{
|
||||
if (cardItem.Card.Attributes.Health.Value <= 0)
|
||||
Dead(cardItem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all card nodes from the scene tree, clears the card list, and triggers a redraw.
|
||||
/// Called by <see cref="CardBattleCore.OnBattleCleared"/> when the battle scene is cleaned up.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var card in CardList.ToList())
|
||||
{
|
||||
if (GodotObject.IsInstanceValid(card))
|
||||
card.QueueFree();
|
||||
}
|
||||
CardList.Clear();
|
||||
activeTweens.Clear();
|
||||
Redraw(animate: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates and applies positions for all cards in the list. When <paramref name="animate"/>
|
||||
/// is <c>true</c>, cards tween to their new positions with a cubic ease-out; otherwise they
|
||||
/// snap instantly. Called after cards are added, removed, or when a selection/unselection
|
||||
/// changes the sort order.
|
||||
/// </summary>
|
||||
public void Redraw(bool animate = true)
|
||||
{
|
||||
for (int i = 0; i < CardList.Count; i++)
|
||||
{
|
||||
var card = CardList[i];
|
||||
if (card == null)
|
||||
continue;
|
||||
|
||||
var (row, col) = GetRowCol(i);
|
||||
SetCardPosition(card, i, row, col, animate);
|
||||
}
|
||||
}
|
||||
|
||||
private (int row, int col) GetRowCol(int index)
|
||||
{
|
||||
if (!UseRows || index < MAX_CARDS_ROW)
|
||||
return (0, index);
|
||||
|
||||
if (index < MAX_CARDS_ROW * 2)
|
||||
return (1, index - MAX_CARDS_ROW);
|
||||
|
||||
return (2, index - MAX_CARDS_ROW * 2);
|
||||
}
|
||||
|
||||
private int CalcBaseZIndex(int row, int col)
|
||||
{
|
||||
return row * 20 + col;
|
||||
}
|
||||
|
||||
private Vector2 CalcTargetPosition(CharacterCardNode card, int row, int col)
|
||||
{
|
||||
var panelSize = Size;
|
||||
float effectiveWidth = card.Size.X * card.Scale.X;
|
||||
float effectiveHeight = card.Size.Y * card.Scale.Y;
|
||||
|
||||
float y = row * (effectiveHeight + cellOffset);
|
||||
|
||||
if (AlignType == "Left")
|
||||
return new Vector2((cellOffset + effectiveWidth) * col, y);
|
||||
else
|
||||
return new Vector2(panelSize.X - (cellOffset + effectiveWidth) * (col + 1), y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates how many rows are needed to display <paramref name="enemyCount"/> enemies
|
||||
/// when <see cref="UseRows"/> is enabled. Returns 1, 2, or 3.
|
||||
/// Called by <see cref="CardBattleCore.CalculateCardScale"/> to determine maximum card scale.
|
||||
/// </summary>
|
||||
public int GetCurrentRowCount(int enemyCount)
|
||||
{
|
||||
if (!UseRows)
|
||||
return 1;
|
||||
|
||||
if (enemyCount <= MAX_CARDS_ROW)
|
||||
return 1;
|
||||
if (enemyCount <= MAX_CARDS_ROW * 2)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum cards per row constant (<c>MAX_CARDS_ROW = 10</c>).
|
||||
/// Used by <see cref="CardBattleCore.CalculateCardScale"/> for scale calculations.
|
||||
/// </summary>
|
||||
public int GetMaxCardsPerRow()
|
||||
{
|
||||
return MAX_CARDS_ROW;
|
||||
}
|
||||
|
||||
private void SetCardPosition(CharacterCardNode card, int index, int row, int col, bool animate)
|
||||
{
|
||||
if (card == null)
|
||||
return;
|
||||
|
||||
var targetPos = CalcTargetPosition(card, row, col);
|
||||
|
||||
if (activeTweens.TryGetValue(card, out var prevTween) && prevTween != null && prevTween.IsValid())
|
||||
prevTween.Kill();
|
||||
|
||||
if (animate)
|
||||
{
|
||||
var tween = CreateTween();
|
||||
tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
tween.TweenProperty(card, "position", targetPos, 0.3f);
|
||||
activeTweens[card] = tween;
|
||||
}
|
||||
else
|
||||
{
|
||||
card.Position = targetPos;
|
||||
activeTweens.Remove(card);
|
||||
}
|
||||
|
||||
var baseZ = CalcBaseZIndex(row, col);
|
||||
card.SetBaseZIndex(baseZ);
|
||||
|
||||
if (content != null)
|
||||
content.MoveChild(card, index);
|
||||
else
|
||||
MoveChild(card, index);
|
||||
card.UpdateInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the card as dead and starts the <see cref="CardDeathAnimation"/> dissolve sequence.
|
||||
/// Once the animation completes, the card is removed from <see cref="CardList"/>,
|
||||
/// <see cref="OnCardDead"/> is fired, and the remaining cards are redrawn.
|
||||
/// Triggered by the health-change handler when a character's health reaches zero.
|
||||
/// </summary>
|
||||
public void Dead(CharacterCardNode characterCardNode)
|
||||
{
|
||||
if (characterCardNode == null || characterCardNode.IsDead)
|
||||
return;
|
||||
|
||||
characterCardNode.IsDead = true;
|
||||
var animator = new Cthangover.CardBattle.UI.CardDeathAnimation();
|
||||
characterCardNode.AddChild(animator);
|
||||
animator.StartAnimation(characterCardNode, () =>
|
||||
{
|
||||
OnCardDead?.Invoke(characterCardNode);
|
||||
CardList.Remove(characterCardNode);
|
||||
activeTweens.Remove(characterCardNode);
|
||||
Redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
-101
@@ -1,101 +1,101 @@
|
||||
using System;
|
||||
using Cthangover.Core.Mods;
|
||||
using Cthangover.Core.Mods.Resolvers;
|
||||
using Cthangover.Core.UI;
|
||||
using Godot;
|
||||
|
||||
namespace Cthangover.CardBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Plays a death animation on a <see cref="CharacterCardNode"/> when its health reaches zero.
|
||||
/// The card simultaneously fades out, shrinks toward the screen center, spins 360 degrees,
|
||||
/// and applies a dissolve shader to all its <see cref="CharacterCardNode.AllImages"/> textures.
|
||||
/// The dissolve shader (<c>"scene_transition"</c>) is resolved once via <see cref="ModManager"/>
|
||||
/// and cached statically. Created and started by <see cref="BattleCardPanel.Dead"/>.
|
||||
/// </summary>
|
||||
public partial class CardDeathAnimation : Node
|
||||
{
|
||||
private CharacterCardNode card;
|
||||
private Vector2 center;
|
||||
private Action onComplete;
|
||||
|
||||
private static Shader dissolveShader;
|
||||
|
||||
/// <summary>
|
||||
/// Begins the death animation sequence. Calculates a target position at the viewport center
|
||||
/// (relative to the card's parent), starts fading all child <see cref="Label"/> nodes,
|
||||
/// applies the dissolve shader material to all card images, and tweens position/rotation/alpha.
|
||||
/// On completion, invokes <paramref name="onComplete"/> and frees this node.
|
||||
/// </summary>
|
||||
public void StartAnimation(CharacterCardNode card, Action onComplete)
|
||||
{
|
||||
this.card = card;
|
||||
this.onComplete = onComplete;
|
||||
|
||||
var viewportCenter = card.GetViewport().GetVisibleRect().Size / 2;
|
||||
center = card.GetParent<Control>() is Control parent
|
||||
? viewportCenter - parent.GlobalPosition
|
||||
: viewportCenter - card.GlobalPosition + card.Position;
|
||||
|
||||
RunAnimation();
|
||||
}
|
||||
|
||||
private void RunAnimation()
|
||||
{
|
||||
float duration = 0.8f;
|
||||
|
||||
Vector2 endPos = center;
|
||||
|
||||
StartFadeOutRecursive(card, duration);
|
||||
|
||||
if (dissolveShader == null)
|
||||
dissolveShader = Shaders.Resolve("scene_transition");
|
||||
|
||||
if (dissolveShader != null)
|
||||
{
|
||||
foreach (var img in card.AllImages)
|
||||
{
|
||||
var mat = new ShaderMaterial { Shader = dissolveShader };
|
||||
mat.SetShaderParameter("noise_scale", 5f);
|
||||
mat.SetShaderParameter("smoothness", 0.25f);
|
||||
mat.SetShaderParameter("distortion", 0.092f);
|
||||
mat.SetShaderParameter("glow_intensity", 0f);
|
||||
mat.SetShaderParameter("hue_shift", 0f);
|
||||
mat.SetShaderParameter("invert_direction", false);
|
||||
mat.SetShaderParameter("progress", 0f);
|
||||
img.Material = mat;
|
||||
|
||||
var matTween = CreateTween();
|
||||
matTween.TweenMethod(
|
||||
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
|
||||
0f, 1f, duration);
|
||||
}
|
||||
}
|
||||
|
||||
var tween = CreateTween();
|
||||
tween.SetParallel(true);
|
||||
tween.TweenProperty(card, "position", endPos, duration);
|
||||
tween.TweenProperty(card, "rotation", Mathf.DegToRad(360f), duration);
|
||||
tween.TweenProperty(card, "modulate:a", 0f, duration);
|
||||
|
||||
tween.Finished += () =>
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
QueueFree();
|
||||
};
|
||||
}
|
||||
|
||||
private static void StartFadeOutRecursive(Node node, float duration)
|
||||
{
|
||||
foreach (Node child in node.GetChildren())
|
||||
{
|
||||
if (child is Label label)
|
||||
{
|
||||
var t = child.CreateTween();
|
||||
t.TweenProperty(label, "modulate:a", 0f, duration);
|
||||
}
|
||||
StartFadeOutRecursive(child, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Cthangover.Core.Mods;
|
||||
using Cthangover.Core.Mods.Resolvers;
|
||||
using Cthangover.Core.UI;
|
||||
using Godot;
|
||||
|
||||
namespace Cthangover.CardBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Plays a death animation on a <see cref="CharacterCardNode"/> when its health reaches zero.
|
||||
/// The card simultaneously fades out, shrinks toward the screen center, spins 360 degrees,
|
||||
/// and applies a dissolve shader to all its <see cref="CharacterCardNode.AllImages"/> textures.
|
||||
/// The dissolve shader (<c>"scene_transition"</c>) is resolved once via <see cref="ModManager"/>
|
||||
/// and cached statically. Created and started by <see cref="BattleCardPanel.Dead"/>.
|
||||
/// </summary>
|
||||
public partial class CardDeathAnimation : Node
|
||||
{
|
||||
private CharacterCardNode card;
|
||||
private Vector2 center;
|
||||
private Action onComplete;
|
||||
|
||||
private static Shader dissolveShader;
|
||||
|
||||
/// <summary>
|
||||
/// Begins the death animation sequence. Calculates a target position at the viewport center
|
||||
/// (relative to the card's parent), starts fading all child <see cref="Label"/> nodes,
|
||||
/// applies the dissolve shader material to all card images, and tweens position/rotation/alpha.
|
||||
/// On completion, invokes <paramref name="onComplete"/> and frees this node.
|
||||
/// </summary>
|
||||
public void StartAnimation(CharacterCardNode card, Action onComplete)
|
||||
{
|
||||
this.card = card;
|
||||
this.onComplete = onComplete;
|
||||
|
||||
var viewportCenter = card.GetViewport().GetVisibleRect().Size / 2;
|
||||
center = card.GetParent<Control>() is Control parent
|
||||
? viewportCenter - parent.GlobalPosition
|
||||
: viewportCenter - card.GlobalPosition + card.Position;
|
||||
|
||||
RunAnimation();
|
||||
}
|
||||
|
||||
private void RunAnimation()
|
||||
{
|
||||
float duration = 0.8f;
|
||||
|
||||
Vector2 endPos = center;
|
||||
|
||||
StartFadeOutRecursive(card, duration);
|
||||
|
||||
if (dissolveShader == null)
|
||||
dissolveShader = Shaders.Resolve("scene_transition");
|
||||
|
||||
if (dissolveShader != null)
|
||||
{
|
||||
foreach (var img in card.AllImages)
|
||||
{
|
||||
var mat = new ShaderMaterial { Shader = dissolveShader };
|
||||
mat.SetShaderParameter("noise_scale", 5f);
|
||||
mat.SetShaderParameter("smoothness", 0.25f);
|
||||
mat.SetShaderParameter("distortion", 0.092f);
|
||||
mat.SetShaderParameter("glow_intensity", 0f);
|
||||
mat.SetShaderParameter("hue_shift", 0f);
|
||||
mat.SetShaderParameter("invert_direction", false);
|
||||
mat.SetShaderParameter("progress", 0f);
|
||||
img.Material = mat;
|
||||
|
||||
var matTween = CreateTween();
|
||||
matTween.TweenMethod(
|
||||
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
|
||||
0f, 1f, duration);
|
||||
}
|
||||
}
|
||||
|
||||
var tween = CreateTween();
|
||||
tween.SetParallel(true);
|
||||
tween.TweenProperty(card, "position", endPos, duration);
|
||||
tween.TweenProperty(card, "rotation", Mathf.DegToRad(360f), duration);
|
||||
tween.TweenProperty(card, "modulate:a", 0f, duration);
|
||||
|
||||
tween.Finished += () =>
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
QueueFree();
|
||||
};
|
||||
}
|
||||
|
||||
private static void StartFadeOutRecursive(Node node, float duration)
|
||||
{
|
||||
foreach (Node child in node.GetChildren())
|
||||
{
|
||||
if (child is Label label)
|
||||
{
|
||||
var t = child.CreateTween();
|
||||
t.TweenProperty(label, "modulate:a", 0f, duration);
|
||||
}
|
||||
StartFadeOutRecursive(child, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-47
@@ -1,48 +1,48 @@
|
||||
using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
namespace Cthangover.CardBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A styled button widget that allows the player to manually end their turn.
|
||||
/// Displayed during the player's turn and hidden during enemy turns and battle cleanup.
|
||||
/// Fires <see cref="OnPressed"/> when clicked, which <see cref="CardBattleCore"/> binds
|
||||
/// to <c>OnPlayerTurnEnd</c>. The button text is translated via the <c>"battle/end_turn"</c> key.
|
||||
/// </summary>
|
||||
public partial class EndTurnButton : ModWidget
|
||||
{
|
||||
private Button _button;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the button is pressed. Subscribed by <see cref="CardBattleCore.OnEndTurnPressed"/>.
|
||||
/// </summary>
|
||||
public event System.Action OnPressed;
|
||||
|
||||
/// <summary>Builds the end-turn button and binds its pressed event.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
_button = GetNode<Button>("BtnEndTurn");
|
||||
CthangoverTheme.ApplyPrimary(_button);
|
||||
|
||||
Size = CustomMinimumSize;
|
||||
|
||||
_button.Pressed += RaisePressed;
|
||||
}
|
||||
|
||||
private void RaisePressed() => OnPressed?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// Shows or hides the underlying <see cref="Button"/> control.
|
||||
/// Called by <see cref="CardBattleCore"/> during turn transitions and battle cleanup.
|
||||
/// </summary>
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
if (_button != null)
|
||||
_button.Visible = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Cthangover.CardBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A styled button widget that allows the player to manually end their turn.
|
||||
/// Displayed during the player's turn and hidden during enemy turns and battle cleanup.
|
||||
/// Fires <see cref="OnPressed"/> when clicked, which <see cref="CardBattleCore"/> binds
|
||||
/// to <c>OnPlayerTurnEnd</c>. The button text is translated via the <c>"battle/end_turn"</c> key.
|
||||
/// </summary>
|
||||
public partial class EndTurnButton : ModWidget
|
||||
{
|
||||
private Button _button;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the button is pressed. Subscribed by <see cref="CardBattleCore.OnEndTurnPressed"/>.
|
||||
/// </summary>
|
||||
public event System.Action OnPressed;
|
||||
|
||||
/// <summary>Builds the end-turn button and binds its pressed event.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
_button = GetNode<Button>("BtnEndTurn");
|
||||
CthangoverTheme.ApplyPrimary(_button);
|
||||
|
||||
Size = CustomMinimumSize;
|
||||
|
||||
_button.Pressed += RaisePressed;
|
||||
}
|
||||
|
||||
private void RaisePressed() => OnPressed?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// Shows or hides the underlying <see cref="Button"/> control.
|
||||
/// Called by <see cref="CardBattleCore"/> during turn transitions and battle cleanup.
|
||||
/// </summary>
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
if (_button != null)
|
||||
_button.Visible = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user