diff --git a/locale/ui_en.properties b/locale/ui_en.properties
index 4234c22..2ce4417 100644
--- a/locale/ui_en.properties
+++ b/locale/ui_en.properties
@@ -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
diff --git a/locale/ui_ru.properties b/locale/ui_ru.properties
index 3b5fe6c..1650a31 100644
--- a/locale/ui_ru.properties
+++ b/locale/ui_ru.properties
@@ -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=Предметы
diff --git a/manifest.json b/manifest.json
index f1ce712..966deb0 100644
--- a/manifest.json
+++ b/manifest.json
@@ -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"
diff --git a/scenes/ff_character_widget.tscn b/scenes/ff_character_widget.tscn
index 6e2e659..4abeb56 100644
--- a/scenes/ff_character_widget.tscn
+++ b/scenes/ff_character_widget.tscn
@@ -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
diff --git a/src/Actions/FFAbstractAnimation.cs b/src/Actions/FFAbstractAnimation.cs
index 3803642..b0f1d83 100644
--- a/src/Actions/FFAbstractAnimation.cs
+++ b/src/Actions/FFAbstractAnimation.cs
@@ -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();
}
+ /// True while the widget is still in the tree and not queued for deletion.
+ protected static bool IsAlive(FFCharacterWidget widget)
+ => GodotObject.IsInstanceValid(widget) && !widget.IsQueuedForDeletion();
+
/// Captures the initial world positions of source and target, then delegates to subclass setup.
public void DoStart()
{
@@ -71,8 +79,11 @@ namespace Cthangover.FFBattle.Actions
DoInternalEnd();
}
+ /// Frame tick of the animation; implementors advance phases and return true when finished.
protected abstract bool DoInternalAction();
+ /// Phase setup called once by after positions are captured.
protected abstract void DoInternalStart();
+ /// Cleanup called once by — restore captured positions here.
protected abstract void DoInternalEnd();
/// Quadratic ease-out: fast start, decelerating to the target. Used for return-to-start motion.
diff --git a/src/Actions/FFAttackAnimation.cs b/src/Actions/FFAttackAnimation.cs
index 6581038..590cc84 100644
--- a/src/Actions/FFAttackAnimation.cs
+++ b/src/Actions/FFAttackAnimation.cs
@@ -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) { }
+ /// Starts the attack sequence: compute the stop point short of the target and reset the phase clock.
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;
}
+ /// Advances move-in → impact (damage via , hit flash/shake, damage popup) → move-back.
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,9 +110,11 @@ namespace Cthangover.FFBattle.Actions
return false;
}
+ /// Returns the attacker to its original position and refreshes both widgets.
protected override void DoInternalEnd()
{
- Source.GlobalPosition = SourcePos;
+ if (IsAlive(Source))
+ Source.GlobalPosition = SourcePos;
Source.UpdateInfo();
Target.UpdateInfo();
}
diff --git a/src/Actions/FFDamageExecutor.cs b/src/Actions/FFDamageExecutor.cs
index a2747ac..ac3a6b7 100644
--- a/src/Actions/FFDamageExecutor.cs
+++ b/src/Actions/FFDamageExecutor.cs
@@ -16,8 +16,10 @@ namespace Cthangover.FFBattle.Actions
///
public class FFDamageExecutor : ActionBase
{
+ /// Hub registry key for this executor.
public override string ID => "FFDamageExecutor";
+ /// Applies attack damage with status hooks and defence absorption; consumes the action's required points.
public override ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
{
if (target == null || user == null || !CheckRequiredAndUsePoint(action, user))
diff --git a/src/Actions/FFDefenceExecutor.cs b/src/Actions/FFDefenceExecutor.cs
index b7270a6..d4e1007 100644
--- a/src/Actions/FFDefenceExecutor.cs
+++ b/src/Actions/FFDefenceExecutor.cs
@@ -12,8 +12,10 @@ namespace Cthangover.FFBattle.Actions
///
public class FFDefenceExecutor : ActionBase
{
+ /// Hub registry key for this executor.
public override string ID => "FFDefenceExecutor";
+ /// Adds the action's "Defence" value (default 4) to the target's defence shield.
public override ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
{
if (target == null || user == null || !CheckRequiredAndUsePoint(action, user))
diff --git a/src/Actions/FFDefendAnimation.cs b/src/Actions/FFDefendAnimation.cs
index 7ecc137..c5056c1 100644
--- a/src/Actions/FFDefendAnimation.cs
+++ b/src/Actions/FFDefendAnimation.cs
@@ -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) { }
+ /// Starts the defend sequence from the bounce phase and captures the glow state.
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);
}
+ /// Advances bounce → glow (defence application with a green flash) → settle.
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,9 +108,11 @@ namespace Cthangover.FFBattle.Actions
return false;
}
+ /// Returns the defender to its original position and refreshes both widgets.
protected override void DoInternalEnd()
{
- Source.GlobalPosition = SourcePos;
+ if (IsAlive(Source))
+ Source.GlobalPosition = SourcePos;
Source.UpdateInfo();
Target.UpdateInfo();
}
diff --git a/src/Actions/FFItemAnimation.cs b/src/Actions/FFItemAnimation.cs
index 63b5e16..60d6eab 100644
--- a/src/Actions/FFItemAnimation.cs
+++ b/src/Actions/FFItemAnimation.cs
@@ -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;
}
+ /// Spawns the raised item icon above the user and starts the item sequence.
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
}
}
+ /// Advances raise-item → apply (item use with effect flash) → lower-icon.
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,9 +127,11 @@ namespace Cthangover.FFBattle.Actions
return false;
}
+ /// Frees the item icon, returns the user to its original position and refreshes both widgets.
protected override void DoInternalEnd()
{
- Source.GlobalPosition = SourcePos;
+ if (IsAlive(Source))
+ Source.GlobalPosition = SourcePos;
if (_itemIcon != null)
{
diff --git a/src/Actions/FFStunExecutor.cs b/src/Actions/FFStunExecutor.cs
index c19d7ae..f746b13 100644
--- a/src/Actions/FFStunExecutor.cs
+++ b/src/Actions/FFStunExecutor.cs
@@ -13,8 +13,10 @@ namespace Cthangover.FFBattle.Actions
///
public class FFStunExecutor : ActionBase
{
+ /// Hub registry key for this executor.
public override string ID => "FFStunExecutor";
+ /// Adds the stun effect for the action's "Turn" count (default 3); stun-immune targets consume the points but stay un-stunned.
public override ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
{
if (target == null || user == null || !CheckRequiredAndUsePoint(action, user))
diff --git a/src/UI/FFActionDetailPanel.cs b/src/UI/FFActionDetailPanel.cs
index 135f89a..d3d866f 100644
--- a/src/UI/FFActionDetailPanel.cs
+++ b/src/UI/FFActionDetailPanel.cs
@@ -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
{
///
@@ -30,6 +32,7 @@ namespace Cthangover.FFBattle.UI
private PanelContainer _note;
private Label _noteLabel;
+ /// Builds the action detail passport panel (description plate shown when an action is inspected).
protected override void Construct()
{
CthangoverTheme.Apply(this);
diff --git a/src/UI/FFBattleBanner.cs b/src/UI/FFBattleBanner.cs
index 3fcdc92..8341ccc 100644
--- a/src/UI/FFBattleBanner.cs
+++ b/src/UI/FFBattleBanner.cs
@@ -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
{
///
@@ -16,6 +18,7 @@ namespace Cthangover.FFBattle.UI
private Label _label;
private Tween _tween;
+ /// Builds the centre-screen turn/outcome banner label.
protected override void Construct()
{
CthangoverTheme.Apply(this);
diff --git a/src/UI/FFBattleController.cs b/src/UI/FFBattleController.cs
index b134b98..6ad1e63 100644
--- a/src/UI/FFBattleController.cs
+++ b/src/UI/FFBattleController.cs
@@ -1,6 +1,7 @@
using Cthangover.Core.UI;
using Godot;
+using Cthangover.Mods.Interface.Base;
namespace Cthangover.FFBattle.UI
{
///
@@ -77,6 +78,7 @@ namespace Cthangover.FFBattle.UI
ProcessMode = ProcessModeEnum.Always;
}
+ /// Routes menu/confirm input into the FF battle state machine (target select, turn advance).
protected override void OnInput(InputEvent @event)
{
if (State == FFBattleState.AnimationPlaying || State == FFBattleState.Ended)
diff --git a/src/UI/FFBattleHud.cs b/src/UI/FFBattleHud.cs
index 06789d4..001ac20 100644
--- a/src/UI/FFBattleHud.cs
+++ b/src/UI/FFBattleHud.cs
@@ -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
{
///
@@ -21,6 +23,7 @@ namespace Cthangover.FFBattle.UI
private Label _enemiesValue;
private Label _alliesValue;
+ /// Builds the FF battle HUD frame: command menu host, speed control and log toggle.
protected override void Construct()
{
CthangoverTheme.Apply(this);
diff --git a/src/UI/FFBattleLogPanel.cs b/src/UI/FFBattleLogPanel.cs
index 5551b18..4d8027e 100644
--- a/src/UI/FFBattleLogPanel.cs
+++ b/src/UI/FFBattleLogPanel.cs
@@ -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
{
///
@@ -23,6 +25,7 @@ namespace Cthangover.FFBattle.UI
private ScrollContainer _scroll;
private VBoxContainer _rows;
+ /// Builds the scrollable FF battle log with coloured entries per .
protected override void Construct()
{
CthangoverTheme.Apply(this);
diff --git a/src/UI/FFBattleTargetHint.cs b/src/UI/FFBattleTargetHint.cs
index 2882dd1..9f3e0a8 100644
--- a/src/UI/FFBattleTargetHint.cs
+++ b/src/UI/FFBattleTargetHint.cs
@@ -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
{
///
@@ -17,6 +19,7 @@ namespace Cthangover.FFBattle.UI
private Label _icon;
private Label _text;
+ /// Builds the floating hint shown above the hovered target.
protected override void Construct()
{
CthangoverTheme.Apply(this);
diff --git a/src/UI/FFCharacterWidget.cs b/src/UI/FFCharacterWidget.cs
index 0c0c272..c3edc35 100644
--- a/src/UI/FFCharacterWidget.cs
+++ b/src/UI/FFCharacterWidget.cs
@@ -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
{
///
@@ -15,7 +17,10 @@ namespace Cthangover.FFBattle.UI
/// only fetches the nodes, dresses them with 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
+ /// (: track → health fill → blue
+ /// defence segment drawn past the health edge while
+ /// absorbs damage), action point pips
/// (party) or attack caption (enemies) and the defence shield chip.
///
/// 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
/// Exposes this widget as a for layout calculations.
public Control ControlNode => this;
+ /// Builds the character widget: portrait frame, HP bar and status chips.
protected override void Construct()
{
CthangoverTheme.Apply(this);
@@ -70,7 +77,8 @@ namespace Cthangover.FFBattle.UI
_nameLabel = GetNode