upd: sync local changes
Fix enemy turn hanging when a lethal hit frees the target widget: UpdateInfo skips freed widgets, animations finish early and restore the attacker only while it is alive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ba2c08b1dc
commit
00aa70e302
21 files changed
+119
-31
No files matched your search
@@ -1,5 +1,5 @@
|
||||
mod_ff_battle_name = FFBattle
|
||||
mod_ff_battle_desc = Final Fantasy style battle system
|
||||
mod_ff_battle_desc = Final Fantasy style battle system on top of turn_battle_base: menu-driven commands (attack/defence/item/stun), enemy and party panels, per-action animations and executors, battle HUD and log.
|
||||
|
||||
ff_battle/menu_attack = Attack
|
||||
ff_battle/menu_items = Items
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod_ff_battle_name=FF Битва
|
||||
mod_ff_battle_desc=Боевая система в стиле Final Fantasy
|
||||
mod_ff_battle_desc=Боевая система в стиле Final Fantasy на платформе turn_battle_base: командное меню (атака/защита/предмет/оглушение), панели партии и врагов, анимации и исполнители действий, HUD и лог боя.
|
||||
|
||||
ff_battle/menu_attack=Атака
|
||||
ff_battle/menu_items=Предметы
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"name": "FFBattle",
|
||||
"version": "1.0.0",
|
||||
"author": "ct",
|
||||
"description": "Final Fantasy style battle system",
|
||||
"description": "Final Fantasy style battle system on top of turn_battle_base: menu-driven commands (attack/defence/item/stun), enemy and party panels, per-action animations and executors, battle HUD and log.",
|
||||
"category": "gameplay",
|
||||
"sources": [
|
||||
"src/**/*.cs"
|
||||
|
||||
@@ -66,10 +66,28 @@ layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
theme_override_font_sizes/font_size = 11
|
||||
|
||||
[node name="HpBar" type="ProgressBar" parent="Info/Column"]
|
||||
[node name="HpBarStack" type="Control" parent="Info/Column"]
|
||||
custom_minimum_size = Vector2(0, 8)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="ShieldBar" type="ProgressBar" parent="Info/Column/HpBarStack"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
show_percentage = false
|
||||
|
||||
[node name="HpBar" type="ProgressBar" parent="Info/Column/HpBarStack"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
show_percentage = false
|
||||
|
||||
|
||||
@@ -49,9 +49,17 @@ namespace Cthangover.FFBattle.Actions
|
||||
{
|
||||
if (Source == null || Target == null || Action == null)
|
||||
return true;
|
||||
// The target's death animation may free its widget mid-animation;
|
||||
// finish early and let DoEnd snap the source back.
|
||||
if (!IsAlive(Source) || !IsAlive(Target))
|
||||
return true;
|
||||
return DoInternalAction();
|
||||
}
|
||||
|
||||
/// <summary>True while the widget is still in the tree and not queued for deletion.</summary>
|
||||
protected static bool IsAlive(FFCharacterWidget widget)
|
||||
=> GodotObject.IsInstanceValid(widget) && !widget.IsQueuedForDeletion();
|
||||
|
||||
/// <summary>Captures the initial world positions of source and target, then delegates to subclass setup.</summary>
|
||||
public void DoStart()
|
||||
{
|
||||
@@ -71,8 +79,11 @@ namespace Cthangover.FFBattle.Actions
|
||||
DoInternalEnd();
|
||||
}
|
||||
|
||||
/// <summary>Frame tick of the animation; implementors advance phases and return true when finished.</summary>
|
||||
protected abstract bool DoInternalAction();
|
||||
/// <summary>Phase setup called once by <see cref="DoStart"/> after positions are captured.</summary>
|
||||
protected abstract void DoInternalStart();
|
||||
/// <summary>Cleanup called once by <see cref="DoEnd"/> — restore captured positions here.</summary>
|
||||
protected abstract void DoInternalEnd();
|
||||
|
||||
/// <summary>Quadratic ease-out: fast start, decelerating to the target. Used for return-to-start motion.</summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Cthangover.Core.Battle;
|
||||
using Cthangover.Core.Battle;
|
||||
using Cthangover.Core.Battle.Actions;
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.TurnBattle;
|
||||
@@ -29,9 +29,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
public FFAttackAnimation(FFCharacterWidget source, FFCharacterWidget target, ActionCharacter action, float speed = 1f)
|
||||
: base(source, target, action, speed) { }
|
||||
|
||||
/// <summary>Starts the attack sequence: compute the stop point short of the target and reset the phase clock.</summary>
|
||||
protected override void DoInternalStart()
|
||||
{
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
_damageApplied = false;
|
||||
_phase = Phase.MoveForward;
|
||||
|
||||
@@ -39,9 +40,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
_midPoint = TargetPos - dir * 30f;
|
||||
}
|
||||
|
||||
/// <summary>Advances move-in → impact (damage via <see cref="ActionExecutorHub"/>, hit flash/shake, damage popup) → move-back.</summary>
|
||||
protected override bool DoInternalAction()
|
||||
{
|
||||
var elapsed = (float)(Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Engine.TimeScale;
|
||||
var elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
||||
|
||||
switch (_phase)
|
||||
{
|
||||
@@ -53,7 +55,7 @@ namespace Cthangover.FFBattle.Actions
|
||||
if (progress >= 1f)
|
||||
{
|
||||
_phase = Phase.Impact;
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -86,7 +88,7 @@ namespace Cthangover.FFBattle.Actions
|
||||
if (progress >= 1f)
|
||||
{
|
||||
_phase = Phase.MoveBack;
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -108,8 +110,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Returns the attacker to its original position and refreshes both widgets.</summary>
|
||||
protected override void DoInternalEnd()
|
||||
{
|
||||
if (IsAlive(Source))
|
||||
Source.GlobalPosition = SourcePos;
|
||||
Source.UpdateInfo();
|
||||
Target.UpdateInfo();
|
||||
|
||||
@@ -16,8 +16,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
/// </summary>
|
||||
public class FFDamageExecutor : ActionBase
|
||||
{
|
||||
/// <summary>Hub registry key for this executor.</summary>
|
||||
public override string ID => "FFDamageExecutor";
|
||||
|
||||
/// <summary>Applies attack damage with status hooks and defence absorption; consumes the action's required points.</summary>
|
||||
public override ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
|
||||
{
|
||||
if (target == null || user == null || !CheckRequiredAndUsePoint(action, user))
|
||||
|
||||
@@ -12,8 +12,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
/// </summary>
|
||||
public class FFDefenceExecutor : ActionBase
|
||||
{
|
||||
/// <summary>Hub registry key for this executor.</summary>
|
||||
public override string ID => "FFDefenceExecutor";
|
||||
|
||||
/// <summary>Adds the action's "Defence" value (default 4) to the target's defence shield.</summary>
|
||||
public override ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
|
||||
{
|
||||
if (target == null || user == null || !CheckRequiredAndUsePoint(action, user))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Cthangover.Core.Battle;
|
||||
using Cthangover.Core.Battle;
|
||||
using Cthangover.Core.Battle.Actions;
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.TurnBattle;
|
||||
@@ -30,17 +30,19 @@ namespace Cthangover.FFBattle.Actions
|
||||
public FFDefendAnimation(FFCharacterWidget source, FFCharacterWidget target, ActionCharacter action, float speed = 1f)
|
||||
: base(source, target, action, speed) { }
|
||||
|
||||
/// <summary>Starts the defend sequence from the bounce phase and captures the glow state.</summary>
|
||||
protected override void DoInternalStart()
|
||||
{
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
_effectApplied = false;
|
||||
_phase = Phase.Bounce;
|
||||
_bounceOffset = new Vector2(0, -12f);
|
||||
}
|
||||
|
||||
/// <summary>Advances bounce → glow (defence application with a green flash) → settle.</summary>
|
||||
protected override bool DoInternalAction()
|
||||
{
|
||||
var elapsed = (float)(Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Engine.TimeScale;
|
||||
var elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
||||
|
||||
switch (_phase)
|
||||
{
|
||||
@@ -53,7 +55,7 @@ namespace Cthangover.FFBattle.Actions
|
||||
if (progress >= 1f)
|
||||
{
|
||||
_phase = Phase.Glow;
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -84,7 +86,7 @@ namespace Cthangover.FFBattle.Actions
|
||||
if (progress >= 1f)
|
||||
{
|
||||
_phase = Phase.Recover;
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -106,8 +108,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Returns the defender to its original position and refreshes both widgets.</summary>
|
||||
protected override void DoInternalEnd()
|
||||
{
|
||||
if (IsAlive(Source))
|
||||
Source.GlobalPosition = SourcePos;
|
||||
Source.UpdateInfo();
|
||||
Target.UpdateInfo();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.Core.Items;
|
||||
using Cthangover.FFBattle.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
@@ -31,9 +31,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
_item = item;
|
||||
}
|
||||
|
||||
/// <summary>Spawns the raised item icon above the user and starts the item sequence.</summary>
|
||||
protected override void DoInternalStart()
|
||||
{
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
_effectApplied = false;
|
||||
_phase = Phase.RaiseItem;
|
||||
|
||||
@@ -49,9 +50,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances raise-item → apply (item use with effect flash) → lower-icon.</summary>
|
||||
protected override bool DoInternalAction()
|
||||
{
|
||||
var elapsed = (float)(Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Engine.TimeScale;
|
||||
var elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
||||
|
||||
switch (_phase)
|
||||
{
|
||||
@@ -71,7 +73,7 @@ namespace Cthangover.FFBattle.Actions
|
||||
if (progress >= 1f)
|
||||
{
|
||||
_phase = Phase.ApplyEffect;
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -103,7 +105,7 @@ namespace Cthangover.FFBattle.Actions
|
||||
if (progress >= 1f)
|
||||
{
|
||||
_phase = Phase.Recover;
|
||||
Timestamp = Time.GetTicksUsec() / 1_000_000.0;
|
||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -125,8 +127,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Frees the item icon, returns the user to its original position and refreshes both widgets.</summary>
|
||||
protected override void DoInternalEnd()
|
||||
{
|
||||
if (IsAlive(Source))
|
||||
Source.GlobalPosition = SourcePos;
|
||||
|
||||
if (_itemIcon != null)
|
||||
|
||||
@@ -13,8 +13,10 @@ namespace Cthangover.FFBattle.Actions
|
||||
/// </summary>
|
||||
public class FFStunExecutor : ActionBase
|
||||
{
|
||||
/// <summary>Hub registry key for this executor.</summary>
|
||||
public override string ID => "FFStunExecutor";
|
||||
|
||||
/// <summary>Adds the stun effect for the action's "Turn" count (default 3); stun-immune targets consume the points but stay un-stunned.</summary>
|
||||
public override ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
|
||||
{
|
||||
if (target == null || user == null || !CheckRequiredAndUsePoint(action, user))
|
||||
|
||||
@@ -5,6 +5,8 @@ using Cthangover.Core.UI.CharacterPanel;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -30,6 +32,7 @@ namespace Cthangover.FFBattle.UI
|
||||
private PanelContainer _note;
|
||||
private Label _noteLabel;
|
||||
|
||||
/// <summary>Builds the action detail passport panel (description plate shown when an action is inspected).</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
@@ -2,6 +2,8 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -16,6 +18,7 @@ namespace Cthangover.FFBattle.UI
|
||||
private Label _label;
|
||||
private Tween _tween;
|
||||
|
||||
/// <summary>Builds the centre-screen turn/outcome banner label.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Cthangover.Core.UI;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -77,6 +78,7 @@ namespace Cthangover.FFBattle.UI
|
||||
ProcessMode = ProcessModeEnum.Always;
|
||||
}
|
||||
|
||||
/// <summary>Routes menu/confirm input into the FF battle state machine (target select, turn advance).</summary>
|
||||
protected override void OnInput(InputEvent @event)
|
||||
{
|
||||
if (State == FFBattleState.AnimationPlaying || State == FFBattleState.Ended)
|
||||
|
||||
@@ -2,6 +2,8 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -21,6 +23,7 @@ namespace Cthangover.FFBattle.UI
|
||||
private Label _enemiesValue;
|
||||
private Label _alliesValue;
|
||||
|
||||
/// <summary>Builds the FF battle HUD frame: command menu host, speed control and log toggle.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
@@ -2,6 +2,8 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,6 +25,7 @@ namespace Cthangover.FFBattle.UI
|
||||
private ScrollContainer _scroll;
|
||||
private VBoxContainer _rows;
|
||||
|
||||
/// <summary>Builds the scrollable FF battle log with coloured entries per <see cref="FFBattleLogKind"/>.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
@@ -2,6 +2,8 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -17,6 +19,7 @@ namespace Cthangover.FFBattle.UI
|
||||
private Label _icon;
|
||||
private Label _text;
|
||||
|
||||
/// <summary>Builds the floating hint shown above the hovered target.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
+27
-11
@@ -7,6 +7,8 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,7 +17,10 @@ namespace Cthangover.FFBattle.UI
|
||||
/// only fetches the nodes, dresses them with <see cref="UiTokens"/> styles and
|
||||
/// refreshes their content. Visually the card follows the unified style guide:
|
||||
/// a team accent line on top, a sprite well, status badges in the upper right
|
||||
/// and an info block holding name, HP numbers, HP bar, action point pips
|
||||
/// and an info block holding name, HP numbers, the layered health bar
|
||||
/// (<see cref="UiKit.StyleShieldedHealthBar"/>: track → health fill → blue
|
||||
/// defence segment drawn past the health edge while
|
||||
/// <see cref="CharacterAttributes.Defence"/> absorbs damage), action point pips
|
||||
/// (party) or attack caption (enemies) and the defence shield chip.
|
||||
/// <para/>
|
||||
/// The public surface is animation-facing, not layout-facing:
|
||||
@@ -38,6 +43,7 @@ namespace Cthangover.FFBattle.UI
|
||||
private Panel _teamDot;
|
||||
private Label _nameLabel;
|
||||
private Label _hpNumLabel;
|
||||
private ProgressBar _shieldBar;
|
||||
private ProgressBar _hpBar;
|
||||
private HBoxContainer _pipsBox;
|
||||
private Label _atkLabel;
|
||||
@@ -61,6 +67,7 @@ namespace Cthangover.FFBattle.UI
|
||||
/// <summary>Exposes this widget as a <see cref="Godot.Control"/> for layout calculations.</summary>
|
||||
public Control ControlNode => this;
|
||||
|
||||
/// <summary>Builds the character widget: portrait frame, HP bar and status chips.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
@@ -70,7 +77,8 @@ namespace Cthangover.FFBattle.UI
|
||||
_nameLabel = GetNode<Label>("Info/Column/NameRow/NameLabel");
|
||||
_teamDot = GetNode<Panel>("Info/Column/NameRow/TeamDot");
|
||||
_hpNumLabel = GetNode<Label>("Info/Column/NameRow/HpNumLabel");
|
||||
_hpBar = GetNode<ProgressBar>("Info/Column/HpBar");
|
||||
_shieldBar = GetNode<ProgressBar>("Info/Column/HpBarStack/ShieldBar");
|
||||
_hpBar = GetNode<ProgressBar>("Info/Column/HpBarStack/HpBar");
|
||||
_pipsBox = GetNode<HBoxContainer>("Info/Column/Foot/PipsBox");
|
||||
_atkLabel = GetNode<Label>("Info/Column/Foot/AtkLabel");
|
||||
_shieldChip = GetNode<PanelContainer>("Info/Column/Foot/ShieldChip");
|
||||
@@ -115,28 +123,35 @@ namespace Cthangover.FFBattle.UI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Single refresh entry point for all mutable character state: HP bar and
|
||||
/// numbers with threshold colours, action point pips, shield chip, attack
|
||||
/// caption and the stun badge with its remaining turns. Called by battle
|
||||
/// code and by animations after each resolved action.
|
||||
/// Single refresh entry point for all mutable character state: layered
|
||||
/// HP/defence bar and numbers with threshold colours, action point pips,
|
||||
/// shield chip, attack caption and the stun badge with its remaining
|
||||
/// turns. Called by battle code and by animations after each resolved
|
||||
/// action.
|
||||
/// </summary>
|
||||
public void UpdateInfo()
|
||||
{
|
||||
if (Card?.Attributes == null)
|
||||
// A lethal hit frees the widget through the death animation while
|
||||
// the attack animation and Health.OnChange subscribers still hold it.
|
||||
if (!IsInstanceValid(this) || IsQueuedForDeletion() || Card?.Attributes == null)
|
||||
return;
|
||||
|
||||
var health = Card.Attributes.Health;
|
||||
var percent = Mathf.Clamp(health.Percent, 0f, 1f);
|
||||
var defence = Card.Attributes.Defence?.Value ?? 0;
|
||||
var fillColor = percent > 0.5f ? UiTokens.HpOk : percent > 0.25f ? UiTokens.Warning : UiTokens.HpLow;
|
||||
var textColor = percent > 0.5f ? UiTokens.HpText : percent > 0.25f ? UiTokens.Warning : UiTokens.DangerText;
|
||||
|
||||
if (_hpBar != null)
|
||||
{
|
||||
_hpBar.Value = percent * 100f;
|
||||
_hpBar.AddThemeStyleboxOverride("background", UiKit.Frame(UiTokens.RadiusSm, UiTokens.LineDisabled));
|
||||
_hpBar.AddThemeStyleboxOverride("fill", UiKit.Frame(UiTokens.RadiusSm, fillColor));
|
||||
}
|
||||
|
||||
// Blue defence segment on top of the health fill; collapses onto
|
||||
// the health edge when the shield is gone.
|
||||
UiKit.UpdateShieldedHealthBar(_shieldBar, percent, defence, health.BaseValue);
|
||||
|
||||
if (_hpNumLabel != null)
|
||||
{
|
||||
_hpNumLabel.Text = $"{health.Value}/{health.BaseValue}";
|
||||
@@ -151,7 +166,6 @@ namespace Cthangover.FFBattle.UI
|
||||
|
||||
if (_shieldChip != null)
|
||||
{
|
||||
var defence = Card.Attributes.Defence?.Value ?? 0;
|
||||
_shieldChip.Visible = defence > 0;
|
||||
if (_shieldValue != null)
|
||||
_shieldValue.Text = defence.ToString();
|
||||
@@ -258,6 +272,7 @@ namespace Cthangover.FFBattle.UI
|
||||
tween.SetParallel(true);
|
||||
tween.TweenProperty(this, "modulate:a", 0f, duration * 0.5f);
|
||||
tween.TweenProperty(this, "scale", BaseScale * new Vector2(0.1f, 1.2f), duration);
|
||||
tween.TweenProperty(_shieldBar, "modulate:a", 0f, duration);
|
||||
tween.TweenProperty(_hpBar, "modulate:a", 0f, duration);
|
||||
tween.TweenProperty(_hpNumLabel, "modulate:a", 0f, duration);
|
||||
tween.TweenProperty(_nameLabel, "modulate:a", 0f, duration);
|
||||
@@ -292,10 +307,11 @@ namespace Cthangover.FFBattle.UI
|
||||
_atkLabel.AddThemeColorOverride("font_color", UiTokens.Muted);
|
||||
_teamDot.AddThemeStyleboxOverride("panel", UiKit.Frame(4, UiTokens.Danger));
|
||||
|
||||
var hpBackground = UiKit.Frame(UiTokens.RadiusSm, UiTokens.LineDisabled);
|
||||
var hpFill = UiKit.Frame(UiTokens.RadiusSm, UiTokens.HpOk);
|
||||
_hpBar.AddThemeStyleboxOverride("background", hpBackground);
|
||||
_hpBar.AddThemeStyleboxOverride("fill", hpFill);
|
||||
// Layered pie: the shield bar owns the track and the blue defence
|
||||
// fill; the health bar paints only its fill on top of it.
|
||||
UiKit.StyleShieldedHealthBar(_shieldBar, _hpBar);
|
||||
|
||||
var shieldStyle = UiKit.Frame(UiTokens.RadiusXs, UiTokens.StatBg, UiTokens.StatLine);
|
||||
shieldStyle.ContentMarginLeft = 7;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,6 +34,7 @@ namespace Cthangover.FFBattle.UI
|
||||
/// <summary>Raised when an enemy widget is clicked (used for target selection).</summary>
|
||||
public event System.Action<FFCharacterWidget> OnEnemyClicked;
|
||||
|
||||
/// <summary>Builds the enemy grid panel and lays out enemy widgets.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
@@ -3,6 +3,8 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -78,6 +80,7 @@ namespace Cthangover.FFBattle.UI
|
||||
/// <summary>Raised when the menu is cancelled (right click, or Escape handled externally).</summary>
|
||||
public event System.Action OnCancelled;
|
||||
|
||||
/// <summary>Builds the command menu list (Attack/Defence/Item/...) used during the player turn.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
@@ -251,6 +254,7 @@ namespace Cthangover.FFBattle.UI
|
||||
_itemLabels.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Frees the menu entries and disconnects selection handlers when the panel closes.</summary>
|
||||
protected override void Destruct()
|
||||
{
|
||||
ClearEntries();
|
||||
|
||||
@@ -6,6 +6,7 @@ using Cthangover.Core.UI;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Mods.Interface.Base.Lists;
|
||||
namespace Cthangover.FFBattle.UI
|
||||
{
|
||||
/// <summary>
|
||||
@@ -26,6 +27,7 @@ namespace Cthangover.FFBattle.UI
|
||||
/// <summary>Raised when a player widget is clicked — routed to <see cref="FFBattleCore"/> for character selection.</summary>
|
||||
public event System.Action<FFCharacterWidget> OnWidgetClicked;
|
||||
|
||||
/// <summary>Builds the bottom player-party panel hosting one widget per party member.</summary>
|
||||
protected override void Construct()
|
||||
{
|
||||
CthangoverTheme.Apply(this);
|
||||
|
||||
Reference in new issue
Block a user