diff --git a/collections/card_novel.json b/collections/card_novel.json index f5f8677..12b261b 100644 --- a/collections/card_novel.json +++ b/collections/card_novel.json @@ -1,37 +1,53 @@ { "id": "card_novel", + "kind": "game", "name": "Card Novel", "author": "ct", "version": "1.0.0", "description": "A visual novel with turn-based battles resolved as a card game: story core, battle engine, card layer and the novel interface.", + "locale": [ + { + "lang": "ru", + "name": "Карточная новелла", + "description": "Визуальная новелла с пошаговыми боями в виде карточной игры: сюжет, боевой движок, карты и интерфейс новеллы." + } + ], "mechanics": [ { "id": "vn_core", "name": "Novel Core", + "role": "story", "description": "Scenes, scenarios, dialogue and the story engine.", + "locale": [ + { "lang": "ru", "name": "Ядро новеллы", "description": "Сцены, сценарии, диалоги и движок истории." } + ], "mods": ["visual_novel_core>=1"] }, { "id": "story", "name": "Story", + "role": "content", "description": "The bundled story content.", "mods": ["murakami>=1"] }, { "id": "battle", "name": "Turn Battle", + "role": "battle", "description": "Turn-based combat foundation.", "mods": ["turn_battle_base>=1"] }, { "id": "cards", "name": "Card Game", + "role": "cards", "description": "Card hands, decks and card-driven battle actions.", "mods": ["card_battle>=1"] }, { "id": "interface", "name": "Interface", + "role": "ui", "description": "Shared GUI panels and the narrative HUD.", "mods": ["interface>=1"] } diff --git a/locale/ui_en.properties b/locale/ui_en.properties index 4fbcf76..3124775 100644 --- a/locale/ui_en.properties +++ b/locale/ui_en.properties @@ -1,5 +1,5 @@ mod_card_battle_name=CardBattle -mod_card_battle_desc=Card battle mechanics +mod_card_battle_desc=Card battle implementation on top of turn_battle_base: an action-card hand with attack/defence values, character cards, battle HUD, log and banners, end-turn flow and its own battle sounds. battle/end_turn=End turn diff --git a/locale/ui_ru.properties b/locale/ui_ru.properties index 4be24b8..d7205a1 100644 --- a/locale/ui_ru.properties +++ b/locale/ui_ru.properties @@ -1,5 +1,5 @@ -mod_card_battle_name=Карточная битва -mod_card_battle_desc=Механики карточных сражений +mod_card_battle_name=Карточная битва +mod_card_battle_desc=Карточные сражения на платформе turn_battle_base: рука боевых карточек (атака/защита), карточки персонажей, боевой HUD, лог и баннеры, завершение хода и свои звуки боя. battle/end_turn=Конец хода diff --git a/manifest.json b/manifest.json index cd2e31e..71fedb8 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "CardBattle", "version": "1.0.0", "author": "ct", - "description": "Card battle mechanics", + "description": "Card battle implementation on top of turn_battle_base: an action-card hand with attack/defence values, character cards, battle HUD, log and banners, end-turn flow and its own battle sounds.", "category": "gameplay", "sources": [ "src/**/*.cs" diff --git a/mod.jpg b/mod.jpg new file mode 100644 index 0000000..06f431e Binary files /dev/null and b/mod.jpg differ diff --git a/mod.png b/mod.png deleted file mode 100644 index 2edfb90..0000000 Binary files a/mod.png and /dev/null differ diff --git a/scenes/character_card.tscn b/scenes/character_card.tscn index 15b154a..56f4e46 100644 --- a/scenes/character_card.tscn +++ b/scenes/character_card.tscn @@ -1,4 +1,4 @@ -[gd_scene format=3] +[gd_scene format=3] [ext_resource type="Script" path="src/UI/CharacterCardNode.cs" id="1_script"] @@ -142,10 +142,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, 7) 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/AbstractBattleAction.cs b/src/Actions/AbstractBattleAction.cs index 826ed9a..0511ba4 100644 --- a/src/Actions/AbstractBattleAction.cs +++ b/src/Actions/AbstractBattleAction.cs @@ -15,16 +15,27 @@ namespace Cthangover.CardBattle.Actions /// public abstract class AbstractBattleAction : IBattleAction { + /// Seconds timestamp of the current phase start (used for phase progress); -1 before the first phase. protected double Timestamp { get; set; } = -1; + /// Playback speed multiplier applied to all phase durations. protected float Speed { get; set; } + /// The action definition whose stats drive the animation. protected ActionCharacter Action { get; set; } + /// Acting card node. protected CharacterCardNode Source { get; set; } + /// Receiving card node. protected CharacterCardNode Target { get; set; } + /// Visual control of the source card (null-safe abort when freed mid-animation). protected Control SourceControl { get; set; } + /// Source position captured in for restoration in . protected Vector2 SourcePos { get; set; } + /// Source rotation captured in . protected float SourceRot { get; set; } + /// Visual control of the target card. protected Control TargetControl { get; set; } + /// Target position captured in . protected Vector2 TargetPos { get; set; } + /// Target rotation captured in . protected float TargetRot { get; set; } /// @@ -41,8 +52,11 @@ namespace Cthangover.CardBattle.Actions return DoInternalAction(); } + /// Frame tick of the animation; implementors advance phases and return true when finished. protected abstract bool DoInternalAction(); + /// Phase setup called once before the first tick. protected abstract void DoInternalStart(); + /// Cleanup called once after completion — restore captured positions/rotations here. protected abstract void DoInternalEnd(); protected AbstractBattleAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1) diff --git a/src/Actions/PhysicsAttackAction.cs b/src/Actions/PhysicsAttackAction.cs index a5a60f6..e83a84f 100644 --- a/src/Actions/PhysicsAttackAction.cs +++ b/src/Actions/PhysicsAttackAction.cs @@ -69,9 +69,10 @@ namespace Cthangover.CardBattle.Actions movementPattern = (MovementPattern)GD.RandRange(0, 5); } + /// Resets the phase clock and starts the attack sequence from the Wait phase. protected override void DoInternalStart() { - Timestamp = Time.GetTicksUsec() / 1_000_000.0; + Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0; damageApplied = false; stunEffectSpawned = false; phase = ActionPhase.Wait; @@ -80,7 +81,7 @@ namespace Cthangover.CardBattle.Actions private float GetPhaseProgress() { - float elapsed = (float)(Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Engine.TimeScale; + float elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale; float duration = GetCurrentPhaseDuration(); return Mathf.Clamp(elapsed / duration, 0f, 1f); } @@ -102,9 +103,10 @@ namespace Cthangover.CardBattle.Actions private void ResetTimestamp() { - Timestamp = Time.GetTicksUsec() / 1_000_000.0; + Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0; } + /// Advances the multi-phase attack: dash in, impact, damage/stun application, return and recover. protected override bool DoInternalAction() { float progress = GetPhaseProgress(); @@ -258,6 +260,7 @@ namespace Cthangover.CardBattle.Actions return false; } + /// Restores the target's captured position, scale and rotation after the attack animation. protected override void DoInternalEnd() { if (SourceControl != null) diff --git a/src/Actions/PhysicsDefenceAction.cs b/src/Actions/PhysicsDefenceAction.cs index 2d00436..bbc7c63 100644 --- a/src/Actions/PhysicsDefenceAction.cs +++ b/src/Actions/PhysicsDefenceAction.cs @@ -42,9 +42,10 @@ namespace Cthangover.CardBattle.Actions public PhysicsDefenceAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1) : base(source, target, action, speed) { } + /// Resets the phase clock and captures the target's original scale and colour. protected override void DoInternalStart() { - Timestamp = Time.GetTicksUsec() / 1_000_000.0; + Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0; effectApplied = false; phase = ActionPhase.Prepare; targetOriginalScale = TargetControl.Scale; @@ -53,7 +54,7 @@ namespace Cthangover.CardBattle.Actions private float GetPhaseProgress() { - float elapsed = (float)(Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Engine.TimeScale; + float elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale; float duration = GetCurrentPhaseDuration(); return Mathf.Clamp(elapsed / duration, 0f, 1f); } @@ -73,9 +74,10 @@ namespace Cthangover.CardBattle.Actions private void ResetTimestamp() { - Timestamp = Time.GetTicksUsec() / 1_000_000.0; + Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0; } + /// Advances the guard animation: approach, impact, defence stat application, return and recover. protected override bool DoInternalAction() { float progress = GetPhaseProgress(); @@ -196,6 +198,7 @@ namespace Cthangover.CardBattle.Actions return false; } + /// Restores the target's captured visual state after the defence animation. protected override void DoInternalEnd() { if (SourceControl != null) diff --git a/src/UI/ActionCardNode.cs b/src/UI/ActionCardNode.cs index 9a2fc1c..c9aaeb4 100644 --- a/src/UI/ActionCardNode.cs +++ b/src/UI/ActionCardNode.cs @@ -4,6 +4,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.CardBattle.UI { /// @@ -50,6 +52,7 @@ namespace Cthangover.CardBattle.UI /// public Control GetControlNode() => this; + /// Builds the action card visuals: artwork, name label and point cost chip from the bound ActionCharacter. protected override void Construct() { CthangoverTheme.Apply(this); diff --git a/src/UI/BattleActionsPanel.cs b/src/UI/BattleActionsPanel.cs index 159c615..2b83546 100644 --- a/src/UI/BattleActionsPanel.cs +++ b/src/UI/BattleActionsPanel.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.CardBattle.UI { /// @@ -28,6 +30,7 @@ namespace Cthangover.CardBattle.UI /// Container that receives action cards; owned by the input controller. public Control Body => _grid; + /// Builds the bottom action bar shown while a card is selected. protected override void Construct() { CthangoverTheme.Apply(this); diff --git a/src/UI/BattleBanner.cs b/src/UI/BattleBanner.cs index 0cfa70e..77334d0 100644 --- a/src/UI/BattleBanner.cs +++ b/src/UI/BattleBanner.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.CardBattle.UI { /// @@ -22,6 +24,7 @@ namespace Cthangover.CardBattle.UI private Label _label; private Tween _tween; + /// Builds the centre-screen turn/victory banner label. protected override void Construct() { CthangoverTheme.Apply(this); diff --git a/src/UI/BattleCardPanel.cs b/src/UI/BattleCardPanel.cs index c6496d8..ed44a62 100644 --- a/src/UI/BattleCardPanel.cs +++ b/src/UI/BattleCardPanel.cs @@ -6,6 +6,8 @@ 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 { /// @@ -48,6 +50,7 @@ namespace Cthangover.CardBattle.UI /// public event System.Action OnCardDead; + /// Builds the card grid container rows for one battle side. protected override void Construct() { CthangoverTheme.Apply(this); diff --git a/src/UI/BattleHud.cs b/src/UI/BattleHud.cs index 1a8e3bf..784fe66 100644 --- a/src/UI/BattleHud.cs +++ b/src/UI/BattleHud.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.CardBattle.UI { /// @@ -19,6 +21,7 @@ namespace Cthangover.CardBattle.UI private Label _roundValue; private Label _enemiesValue; + /// Builds the battle HUD frame: turn indicator, speed control and log button. protected override void Construct() { CthangoverTheme.Apply(this); diff --git a/src/UI/BattleLogPanel.cs b/src/UI/BattleLogPanel.cs index 23c5881..3333dd2 100644 --- a/src/UI/BattleLogPanel.cs +++ b/src/UI/BattleLogPanel.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.CardBattle.UI { /// @@ -25,6 +27,7 @@ namespace Cthangover.CardBattle.UI private ScrollContainer _scroll; private VBoxContainer _rows; + /// Builds the scrollable battle log with coloured entries per . protected override void Construct() { CthangoverTheme.Apply(this); @@ -51,6 +54,7 @@ namespace Cthangover.CardBattle.UI BattleLogHub.Entry += Append; } + /// Unsubscribes from the shared so the freed panel stops receiving entries. protected override void Destruct() { BattleLogHub.Entry -= Append; diff --git a/src/UI/CardBattleScreen.cs b/src/UI/CardBattleScreen.cs index 2092b51..d84e737 100644 --- a/src/UI/CardBattleScreen.cs +++ b/src/UI/CardBattleScreen.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.View; namespace Cthangover.CardBattle.UI { /// @@ -48,6 +50,7 @@ namespace Cthangover.CardBattle.UI /// public CanvasLayer DragLayer { get; private set; } + /// Assembles the full card-battle screen: panels, HUD, banner, log and drag layer. protected override void Construct() { CthangoverTheme.Apply(this); diff --git a/src/UI/CardController.cs b/src/UI/CardController.cs index d060989..9c551a2 100644 --- a/src/UI/CardController.cs +++ b/src/UI/CardController.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Cthangover.Core.Cards; using Cthangover.Core.Audio; using Cthangover.Core.Scenes; @@ -9,6 +9,7 @@ using Cthangover.Core.UI.CharacterPanel; using Cthangover.Core.UI.Theming; using Godot; +using Cthangover.Mods.Interface.Base; namespace Cthangover.CardBattle.UI { /// @@ -70,6 +71,7 @@ namespace Cthangover.CardBattle.UI _panels = new[] { screen.PlayerPanel, screen.EnemyPanel }; } + /// Routes mouse input into card selection and target picking for the player turn. protected override void OnInput(InputEvent @event) { if (Cthangover.Core.Battle.BattleSceneContext.Instance != null && @@ -274,7 +276,7 @@ namespace Cthangover.CardBattle.UI if (source != _selectedCharacter && _selectedCharacter != null) _selectedCharacter.Unselect(); - var audioService = SceneContextNode.Instance?.GetSceneRoot("AudioService"); + var audioService = SceneContextNode.Instance?.GetSceneRoot("AudioService") as IAudioService; audioService?.PlaySound("battle/card_show_actions", SoundType.CardEffect); _selectedCharacter = source; @@ -303,7 +305,7 @@ namespace Cthangover.CardBattle.UI _currentActionCard.Unselect(); _dragValid = true; - var audioService = SceneContextNode.Instance?.GetSceneRoot("AudioService"); + var audioService = SceneContextNode.Instance?.GetSceneRoot("AudioService") as IAudioService; audioService?.PlaySound("battle/card_pick", 4, SoundType.CardEffect); _currentActionCard = source; @@ -382,7 +384,7 @@ namespace Cthangover.CardBattle.UI _originalActionCardParent = null; } - var audioService = SceneContextNode.Instance?.GetSceneRoot("AudioService"); + var audioService = SceneContextNode.Instance?.GetSceneRoot("AudioService") as IAudioService; audioService?.PlaySound("battle/card_drop", 4, SoundType.CardEffect); DoActionToTarget(); diff --git a/src/UI/CharacterCardNode.cs b/src/UI/CharacterCardNode.cs index a78acd1..0a0f2c1 100644 --- a/src/UI/CharacterCardNode.cs +++ b/src/UI/CharacterCardNode.cs @@ -6,13 +6,18 @@ using Cthangover.Core.UI; using Cthangover.Core.UI.Theming; using Godot; +using Cthangover.Mods.Interface.Base.Lists; +using Cthangover.Mods.Interface.Theming; namespace Cthangover.CardBattle.UI { /// /// Party or enemy card on the battle field, rebuilt from the interface /// mockup (mockups/card_battle): full-bleed portrait, team-coloured frame, - /// bottom info block with the name, four stat chips (HP/ЗАЩ/АТК/ОД) and an - /// HP bar, plus stun badge, zap flash and selection frame in the upper right. + /// bottom info block with the name, four stat chips (HP/ЗАЩ/АТК/ОД) and a + /// layered health bar (: + /// track → health fill → blue defence segment drawn past the health edge + /// while absorbs damage), plus + /// stun badge, zap flash and selection frame in the upper right. /// The node tree lives in scenes/character_card.tscn; this class /// fetches the nodes once, dresses them with styles and /// exposes the battle-facing API: is the single @@ -34,6 +39,7 @@ namespace Cthangover.CardBattle.UI private TextureRect _art; private Panel _teamFrame; private Label _nameLabel; + private ProgressBar _shieldBar; private ProgressBar _hpBar; private Label _hpValue; private Label _defValue; @@ -87,6 +93,7 @@ namespace Cthangover.CardBattle.UI /// public Control GetControlNode() => this; + /// Builds the character card: portrait, art frame, selection overlay and stat labels. protected override void Construct() { CthangoverTheme.Apply(this); @@ -94,7 +101,8 @@ namespace Cthangover.CardBattle.UI _art = GetNode("Art"); _teamFrame = GetNode("TeamFrame"); _nameLabel = GetNode