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:
iisaevandClaude Opus 5.5 committed 2026-10-16 16:18:14 +00:00
1 parent ba2c08b1dc
commit 00aa70e302
21 files changed
+119 -31

No files matched your search

+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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"
+20 -2
View File
@@ -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
+11
View File
@@ -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>
+9 -5
View File
@@ -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();
+2
View File
@@ -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))
+2
View File
@@ -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))
+9 -5
View File
@@ -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();
+9 -5
View File
@@ -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)
+2
View File
@@ -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))
+3
View File
@@ -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);
+3
View File
@@ -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);
+2
View File
@@ -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)
+3
View File
@@ -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);
+3
View File
@@ -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);
+3
View File
@@ -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
View File
@@ -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;
+2
View File
@@ -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);
+4
View File
@@ -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();
+2
View File
@@ -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);