using Cthangover.Core.UI.Theming; using Godot; namespace Cthangover.TurnBattle { /// /// Floating damage-number overlay. Self-contained: creates its own Label /// in _Ready, floats upward with random horizontal drift, fades alpha, /// then QueueFrees itself. Static factory methods (SpawnDamage, /// SpawnDefence) instantiate and parent it in one call — damage is red, /// heal is green, defence is blue. MouseFilter is Ignore so clicks pass /// through to cards underneath. /// public partial class ShowDamageBehaviour : Control { private Label _label; private float duration = 1.0f; private float floatDistance = 80f; private float driftRange = 30f; private Vector2 startPosition; /// /// Builds the label on first use. Godot does not dispatch _Ready /// overrides declared in mod assemblies (only methods generated in the core /// project run), so the label cannot be created in _Ready — it is /// created here, right before the first animation starts. /// private void EnsureLabel() { if (_label != null) return; MouseFilter = MouseFilterEnum.Ignore; _label = new Label(); _label.SetAnchorsAndOffsetsPreset(LayoutPreset.Center); _label.HorizontalAlignment = HorizontalAlignment.Center; _label.VerticalAlignment = VerticalAlignment.Center; _label.AddThemeFontSizeOverride("font_size", 28); _label.AddThemeColorOverride("font_outline_color", UiTokens.Alpha(Colors.Black, 1f)); _label.AddThemeConstantOverride("outline_size", 6); AddChild(_label); } /// /// Creates and parents a floating damage (or heal) number. /// Damage is red, heal is green. The label floats upward with /// a random horizontal drift and auto-destructs after the /// tween completes. Skips values <= 0. /// public static void SpawnDamage(int amount, Node parent, Vector2 position, bool isHeal = false) { if (amount <= 0) return; var color = isHeal ? UiTokens.SuccessText : UiTokens.DangerText; var hint = new ShowDamageBehaviour(); parent.AddChild(hint); hint.Init(amount, position, color); } /// /// Creates and parents a floating defence number (blue). /// Same floating behaviour as . /// Skips values <= 0. /// public static void SpawnDefence(int amount, Node parent, Vector2 position) { if (amount <= 0) return; var hint = new ShowDamageBehaviour(); parent.AddChild(hint); hint.Init(amount, position, UiTokens.Info); } /// /// Sets the label text, colour, and world position, then starts /// the floating animation tween. Called by the static factory /// methods after instantiation. /// public void Init(int amount, Vector2 worldPosition, Color? color = null) { EnsureLabel(); _label.Text = amount.ToString(); if (color.HasValue) _label.Modulate = color.Value; GlobalPosition = worldPosition; startPosition = Position; RunAnimation(); } private async void RunAnimation() { float driftX = (float)GD.RandRange(-driftRange, driftRange); Vector2 targetPos = startPosition + new Vector2(driftX, -floatDistance); var tween = CreateTween(); tween.SetParallel(true); tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic); tween.TweenProperty(this, "position", targetPos, duration); var fadeTween = CreateTween(); fadeTween.TweenProperty(_label, "modulate:a", 0f, duration * 0.8f); await ToSignal(tween, "finished"); QueueFree(); } } }