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