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