diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3030c1a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,30 @@
+# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
+* text=auto eol=lf
+
+# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
+*.bat text eol=crlf
+*.cmd text eol=crlf
+
+# Binaries: never converted, never diffed as text.
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.webp binary
+*.svg text
+*.ogg binary
+*.wav binary
+*.mp3 binary
+*.ttf binary
+*.otf binary
+*.woff binary
+*.woff2 binary
+*.dll binary
+*.so binary
+*.dylib binary
+*.pdb binary
+*.exe binary
+*.zip binary
+*.pck binary
+*.moc3 binary
+*.res binary
+*.scn binary
diff --git a/src/Actions/ActionExecutorHub.cs b/src/Actions/ActionExecutorHub.cs
index d7f473a..15fa226 100644
--- a/src/Actions/ActionExecutorHub.cs
+++ b/src/Actions/ActionExecutorHub.cs
@@ -1,79 +1,79 @@
-using System.Collections.Generic;
-using Cthangover.Core.Characters;
-
-namespace Cthangover.Core.Battle.Actions
-{
- ///
- /// Central action-execution dispatcher. Resolves an action ID to an
- /// IActionExecutor in two tiers: first the active provider (set by
- /// the current IBattleCore, allowing per-battle-engine overrides),
- /// then a global fallback registry. This two-level lookup lets mods
- /// supply custom executors for specific battle cores without replacing
- /// the global ones. Execute returns ChangedAttributes to allow the
- /// caller to inspect the result (success/failure, stat deltas).
- ///
- public class ActionExecutorHub
- {
- ///
- /// Singleton hub instance. There is one global dispatcher shared
- /// across all battle cores; cores swap providers via
- /// when they activate.
- ///
- public static readonly ActionExecutorHub Instance = new();
-
- private IActionExecutorProvider _activeProvider;
- private readonly Dictionary _globalExecutors = new();
-
- ///
- /// Installs a per-core executor provider. The active provider is
- /// consulted before the global registry, allowing the current
- /// battle core to override specific action handlers.
- ///
- public void SetActiveProvider(IActionExecutorProvider provider)
- {
- _activeProvider = provider;
- }
-
- ///
- /// Registers a fallback executor in the global pool, keyed by
- /// . Global executors are
- /// used when no active provider supplies a match.
- ///
- public void RegisterGlobal(IActionExecutor executor)
- {
- if (executor != null && !string.IsNullOrEmpty(executor.ActionId))
- _globalExecutors[executor.ActionId] = executor;
- }
-
- ///
- /// Dispatches an action to the appropriate executor. Resolution
- /// order: active provider first, then global registry. Returns
- /// Result = false if no executor is found or if
- /// is null.
- ///
- public ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
- {
- if (action == null)
- return new ChangedAttributes { Result = false };
-
- var executor = Resolve(action.ID);
- if (executor == null)
- return new ChangedAttributes { Result = false };
-
- return executor.Execute(action, user, target);
- }
-
- private IActionExecutor Resolve(string actionId)
- {
- if (_activeProvider != null)
- {
- var executor = _activeProvider.GetExecutor(actionId);
- if (executor != null)
- return executor;
- }
-
- _globalExecutors.TryGetValue(actionId, out var fallback);
- return fallback;
- }
- }
-}
+using System.Collections.Generic;
+using Cthangover.Core.Characters;
+
+namespace Cthangover.Core.Battle.Actions
+{
+ ///
+ /// Central action-execution dispatcher. Resolves an action ID to an
+ /// IActionExecutor in two tiers: first the active provider (set by
+ /// the current IBattleCore, allowing per-battle-engine overrides),
+ /// then a global fallback registry. This two-level lookup lets mods
+ /// supply custom executors for specific battle cores without replacing
+ /// the global ones. Execute returns ChangedAttributes to allow the
+ /// caller to inspect the result (success/failure, stat deltas).
+ ///
+ public class ActionExecutorHub
+ {
+ ///
+ /// Singleton hub instance. There is one global dispatcher shared
+ /// across all battle cores; cores swap providers via
+ /// when they activate.
+ ///
+ public static readonly ActionExecutorHub Instance = new();
+
+ private IActionExecutorProvider _activeProvider;
+ private readonly Dictionary _globalExecutors = new();
+
+ ///
+ /// Installs a per-core executor provider. The active provider is
+ /// consulted before the global registry, allowing the current
+ /// battle core to override specific action handlers.
+ ///
+ public void SetActiveProvider(IActionExecutorProvider provider)
+ {
+ _activeProvider = provider;
+ }
+
+ ///
+ /// Registers a fallback executor in the global pool, keyed by
+ /// . Global executors are
+ /// used when no active provider supplies a match.
+ ///
+ public void RegisterGlobal(IActionExecutor executor)
+ {
+ if (executor != null && !string.IsNullOrEmpty(executor.ActionId))
+ _globalExecutors[executor.ActionId] = executor;
+ }
+
+ ///
+ /// Dispatches an action to the appropriate executor. Resolution
+ /// order: active provider first, then global registry. Returns
+ /// Result = false if no executor is found or if
+ /// is null.
+ ///
+ public ChangedAttributes Execute(ActionCharacter action, Character user, Character target)
+ {
+ if (action == null)
+ return new ChangedAttributes { Result = false };
+
+ var executor = Resolve(action.ID);
+ if (executor == null)
+ return new ChangedAttributes { Result = false };
+
+ return executor.Execute(action, user, target);
+ }
+
+ private IActionExecutor Resolve(string actionId)
+ {
+ if (_activeProvider != null)
+ {
+ var executor = _activeProvider.GetExecutor(actionId);
+ if (executor != null)
+ return executor;
+ }
+
+ _globalExecutors.TryGetValue(actionId, out var fallback);
+ return fallback;
+ }
+ }
+}
diff --git a/src/Actions/BattleActionMachine.cs b/src/Actions/BattleActionMachine.cs
index dbac98a..d20e2ab 100644
--- a/src/Actions/BattleActionMachine.cs
+++ b/src/Actions/BattleActionMachine.cs
@@ -1,122 +1,122 @@
-using System.Collections.Generic;
-using Cthangover.Core.UI;
-using Godot;
-
+using System.Collections.Generic;
+using Cthangover.Core.UI;
+using Godot;
+
using Cthangover.Mods.Interface.Base;
-namespace Cthangover.Core.Battle.Actions
-{
-
- ///
- /// Sequential action queue that processes one IBattleAction per frame
- /// until it signals completion (DoAction returns true), then moves to
- /// the next. Fires OnStopMachine when the queue drains naturally or
- /// is explicitly stopped — used by battle cores to detect when a turn's
- /// animation sequence is finished. Actions can be added before or during
- /// processing; the machine starts automatically once at least one action
- /// is queued and no current action is running.
- ///
- /// Derives from so the engine lifecycle callbacks reach
- /// this runtime-compiled script through the core base class (mod assemblies
- /// have no Godot source generation of their own).
- ///
- public partial class BattleActionMachine : ModNode
- {
-
- ///
- /// Pending action queue. Actions are dequeued one-by-one in the process
- /// hook, starting the next as soon as the current signals completion.
- /// Can be extended while the machine is running — new actions will
- /// be processed after the current queue drains.
- ///
- public List Actions { get; set; } = new();
- private IBattleAction CurrentAction { get; set; }
- private bool _hadActions;
-
- ///
- /// Fired when the machine drains naturally or is explicitly stopped.
- /// Battle cores subscribe to advance the turn phase without polling.
- ///
- public event StopMachineDelegate OnStopMachine;
-
- /// Stops the machine and fires when the node leaves the tree, so subscribers never wait on a dead node.
- protected override void OnExitTree()
- {
- base.OnExitTree();
- StopMachine();
- }
-
- /// Drives the queued actions once per frame; fires when the queue drains.
- protected override void OnProcess(double delta)
- {
- if (Actions.Count == 0 && CurrentAction == null)
- {
- if (_hadActions)
- {
- _hadActions = false;
- StopMachine();
- }
- return;
- }
-
- var action = CurrentAction;
- if (action == null)
- {
- CurrentAction = Actions[0];
- Actions.RemoveAt(0);
- action = CurrentAction;
- _hadActions = true;
- action?.DoStart();
- }
-
- if (action == null)
- {
- _hadActions = false;
- StopMachine();
- return;
- }
-
- if (action.DoAction())
- {
- action.DoEnd();
- CurrentAction = null;
- }
- }
-
- ///
- /// Enqueues a single action. If the machine is idle (no current
- /// action), processing starts on the next _Process tick.
- ///
- public void AddAction(IBattleAction action)
- {
- if (action == null)
- return;
- Actions.Add(action);
- }
-
- ///
- /// Batch-enqueues multiple actions. All are appended to the existing
- /// queue and processed in FIFO order on subsequent frames.
- ///
- public void AddActions(List actions)
- {
- if(actions == null || actions.Count == 0)
- return;
- Actions.AddRange(actions);
- }
-
- ///
- /// Immediately clears the queue, aborts the current action, and
- /// fires . Called on tree exit and
- /// when the queue drains naturally.
- ///
- public void StopMachine()
- {
- CurrentAction = null;
- Actions.Clear();
- _hadActions = false;
- OnStopMachine?.Invoke();
- }
-
- }
-
-}
+namespace Cthangover.Core.Battle.Actions
+{
+
+ ///
+ /// Sequential action queue that processes one IBattleAction per frame
+ /// until it signals completion (DoAction returns true), then moves to
+ /// the next. Fires OnStopMachine when the queue drains naturally or
+ /// is explicitly stopped — used by battle cores to detect when a turn's
+ /// animation sequence is finished. Actions can be added before or during
+ /// processing; the machine starts automatically once at least one action
+ /// is queued and no current action is running.
+ ///
+ /// Derives from so the engine lifecycle callbacks reach
+ /// this runtime-compiled script through the core base class (mod assemblies
+ /// have no Godot source generation of their own).
+ ///
+ public partial class BattleActionMachine : ModNode
+ {
+
+ ///
+ /// Pending action queue. Actions are dequeued one-by-one in the process
+ /// hook, starting the next as soon as the current signals completion.
+ /// Can be extended while the machine is running — new actions will
+ /// be processed after the current queue drains.
+ ///
+ public List Actions { get; set; } = new();
+ private IBattleAction CurrentAction { get; set; }
+ private bool _hadActions;
+
+ ///
+ /// Fired when the machine drains naturally or is explicitly stopped.
+ /// Battle cores subscribe to advance the turn phase without polling.
+ ///
+ public event StopMachineDelegate OnStopMachine;
+
+ /// Stops the machine and fires when the node leaves the tree, so subscribers never wait on a dead node.
+ protected override void OnExitTree()
+ {
+ base.OnExitTree();
+ StopMachine();
+ }
+
+ /// Drives the queued actions once per frame; fires when the queue drains.
+ protected override void OnProcess(double delta)
+ {
+ if (Actions.Count == 0 && CurrentAction == null)
+ {
+ if (_hadActions)
+ {
+ _hadActions = false;
+ StopMachine();
+ }
+ return;
+ }
+
+ var action = CurrentAction;
+ if (action == null)
+ {
+ CurrentAction = Actions[0];
+ Actions.RemoveAt(0);
+ action = CurrentAction;
+ _hadActions = true;
+ action?.DoStart();
+ }
+
+ if (action == null)
+ {
+ _hadActions = false;
+ StopMachine();
+ return;
+ }
+
+ if (action.DoAction())
+ {
+ action.DoEnd();
+ CurrentAction = null;
+ }
+ }
+
+ ///
+ /// Enqueues a single action. If the machine is idle (no current
+ /// action), processing starts on the next _Process tick.
+ ///
+ public void AddAction(IBattleAction action)
+ {
+ if (action == null)
+ return;
+ Actions.Add(action);
+ }
+
+ ///
+ /// Batch-enqueues multiple actions. All are appended to the existing
+ /// queue and processed in FIFO order on subsequent frames.
+ ///
+ public void AddActions(List actions)
+ {
+ if(actions == null || actions.Count == 0)
+ return;
+ Actions.AddRange(actions);
+ }
+
+ ///
+ /// Immediately clears the queue, aborts the current action, and
+ /// fires . Called on tree exit and
+ /// when the queue drains naturally.
+ ///
+ public void StopMachine()
+ {
+ CurrentAction = null;
+ Actions.Clear();
+ _hadActions = false;
+ OnStopMachine?.Invoke();
+ }
+
+ }
+
+}
diff --git a/src/Actions/IActionExecutor.cs b/src/Actions/IActionExecutor.cs
index 4ef1413..6c4ef98 100644
--- a/src/Actions/IActionExecutor.cs
+++ b/src/Actions/IActionExecutor.cs
@@ -1,27 +1,27 @@
-using Cthangover.Core.Characters;
-
-namespace Cthangover.Core.Battle.Actions
-{
- ///
- /// Executes a specific battle action. The executor is keyed by ActionId
- /// (matching the action's ID) so the hub can look it up without
- /// knowing the concrete type. Returns ChangedAttributes so callers
- /// can apply stat deltas and check whether the action succeeded.
- ///
- public interface IActionExecutor
- {
- ///
- /// String key that matches .
- /// The hub uses this to resolve executors without knowing
- /// concrete types.
- ///
- string ActionId { get; }
-
- ///
- /// Applies the action's effects. Receives the action definition
- /// and the user/target instances; returns
- /// with deltas and success/failure.
- ///
- ChangedAttributes Execute(ActionCharacter action, Character user, Character target);
- }
-}
+using Cthangover.Core.Characters;
+
+namespace Cthangover.Core.Battle.Actions
+{
+ ///
+ /// Executes a specific battle action. The executor is keyed by ActionId
+ /// (matching the action's ID) so the hub can look it up without
+ /// knowing the concrete type. Returns ChangedAttributes so callers
+ /// can apply stat deltas and check whether the action succeeded.
+ ///
+ public interface IActionExecutor
+ {
+ ///
+ /// String key that matches .
+ /// The hub uses this to resolve executors without knowing
+ /// concrete types.
+ ///
+ string ActionId { get; }
+
+ ///
+ /// Applies the action's effects. Receives the action definition
+ /// and the user/target instances; returns
+ /// with deltas and success/failure.
+ ///
+ ChangedAttributes Execute(ActionCharacter action, Character user, Character target);
+ }
+}
diff --git a/src/Actions/IActionExecutorProvider.cs b/src/Actions/IActionExecutorProvider.cs
index 71acb13..c2d238b 100644
--- a/src/Actions/IActionExecutorProvider.cs
+++ b/src/Actions/IActionExecutorProvider.cs
@@ -1,18 +1,18 @@
-namespace Cthangover.Core.Battle.Actions
-{
- ///
- /// Typed executor source. Each IBattleCore returns its own provider,
- /// giving cores the ability to supply custom executor sets without
- /// polluting the global registry.
- ///
- public interface IActionExecutorProvider
- {
- ///
- /// Returns the executor registered for ,
- /// or null if no override is configured. Called by
- /// before falling back to the
- /// global registry.
- ///
- IActionExecutor GetExecutor(string actionId);
- }
-}
+namespace Cthangover.Core.Battle.Actions
+{
+ ///
+ /// Typed executor source. Each IBattleCore returns its own provider,
+ /// giving cores the ability to supply custom executor sets without
+ /// polluting the global registry.
+ ///
+ public interface IActionExecutorProvider
+ {
+ ///
+ /// Returns the executor registered for ,
+ /// or null if no override is configured. Called by
+ /// before falling back to the
+ /// global registry.
+ ///
+ IActionExecutor GetExecutor(string actionId);
+ }
+}
diff --git a/src/Actions/IBattleAction.cs b/src/Actions/IBattleAction.cs
index 6fee639..090c9b7 100644
--- a/src/Actions/IBattleAction.cs
+++ b/src/Actions/IBattleAction.cs
@@ -1,32 +1,32 @@
-namespace Cthangover.Core.Battle.Actions
-{
- ///
- /// A single step in the battle action queue (animation, damage, effect).
- /// DoStart is called once when the action begins, DoAction is polled
- /// every frame — it returns true when the action is done — and DoEnd
- /// is the cleanup callback. This polling model lets actions drive
- /// their own duration (e.g. a tween runs until finished) without
- /// the machine needing timers.
- ///
- public interface IBattleAction
- {
- ///
- /// Called once when the action is dequeued and begins execution.
- /// Use for setup: spawning nodes, caching references, starting
- /// tweens or coroutines.
- ///
- void DoStart();
- ///
- /// Polled every frame. Returns true when the action has
- /// finished its work; returns false to remain active.
- /// This model eliminates the need for the machine to manage timers.
- ///
- bool DoAction();
- ///
- /// Cleanup callback fired after returns
- /// true. Use for teardown: freeing nodes, resetting state,
- /// applying final stat deltas.
- ///
- void DoEnd();
- }
-}
+namespace Cthangover.Core.Battle.Actions
+{
+ ///
+ /// A single step in the battle action queue (animation, damage, effect).
+ /// DoStart is called once when the action begins, DoAction is polled
+ /// every frame — it returns true when the action is done — and DoEnd
+ /// is the cleanup callback. This polling model lets actions drive
+ /// their own duration (e.g. a tween runs until finished) without
+ /// the machine needing timers.
+ ///
+ public interface IBattleAction
+ {
+ ///
+ /// Called once when the action is dequeued and begins execution.
+ /// Use for setup: spawning nodes, caching references, starting
+ /// tweens or coroutines.
+ ///
+ void DoStart();
+ ///
+ /// Polled every frame. Returns true when the action has
+ /// finished its work; returns false to remain active.
+ /// This model eliminates the need for the machine to manage timers.
+ ///
+ bool DoAction();
+ ///
+ /// Cleanup callback fired after returns
+ /// true. Use for teardown: freeing nodes, resetting state,
+ /// applying final stat deltas.
+ ///
+ void DoEnd();
+ }
+}
diff --git a/src/Adapters/AnimationRegistry.cs b/src/Adapters/AnimationRegistry.cs
index 019013d..759aa44 100644
--- a/src/Adapters/AnimationRegistry.cs
+++ b/src/Adapters/AnimationRegistry.cs
@@ -1,42 +1,42 @@
-using System.Collections.Generic;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Global animation-player index keyed by AnimationType string.
- /// Mods or cores register players; the action machine looks up the
- /// player for a given animation type. Singleton — there is one set
- /// of players for the entire runtime, but each player can make
- /// per-core decisions internally.
- ///
- public class AnimationRegistry
- {
- ///
- /// Singleton registry. One set of animation players serves the
- /// entire runtime; individual players dispatch per-core via
- /// internal logic.
- ///
- public static readonly AnimationRegistry Instance = new();
-
- private readonly Dictionary _players = new();
-
- ///
- /// Registers a player under .
- /// Replaces any existing player for the same key — last write wins.
- ///
- public void Register(string animType, IBattleAnimationPlayer player)
- {
- _players[animType] = player;
- }
-
- ///
- /// Retrieves the player registered for ,
- /// or null if no match exists.
- ///
- public IBattleAnimationPlayer Get(string animType)
- {
- _players.TryGetValue(animType, out var player);
- return player;
- }
- }
-}
+using System.Collections.Generic;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Global animation-player index keyed by AnimationType string.
+ /// Mods or cores register players; the action machine looks up the
+ /// player for a given animation type. Singleton — there is one set
+ /// of players for the entire runtime, but each player can make
+ /// per-core decisions internally.
+ ///
+ public class AnimationRegistry
+ {
+ ///
+ /// Singleton registry. One set of animation players serves the
+ /// entire runtime; individual players dispatch per-core via
+ /// internal logic.
+ ///
+ public static readonly AnimationRegistry Instance = new();
+
+ private readonly Dictionary _players = new();
+
+ ///
+ /// Registers a player under .
+ /// Replaces any existing player for the same key — last write wins.
+ ///
+ public void Register(string animType, IBattleAnimationPlayer player)
+ {
+ _players[animType] = player;
+ }
+
+ ///
+ /// Retrieves the player registered for ,
+ /// or null if no match exists.
+ ///
+ public IBattleAnimationPlayer Get(string animType)
+ {
+ _players.TryGetValue(animType, out var player);
+ return player;
+ }
+ }
+}
diff --git a/src/Adapters/BattleContextImpl.cs b/src/Adapters/BattleContextImpl.cs
index 5b32fc8..19d4369 100644
--- a/src/Adapters/BattleContextImpl.cs
+++ b/src/Adapters/BattleContextImpl.cs
@@ -1,79 +1,79 @@
-using Cthangover.Core.Characters;
-using Godot;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Default IBattleContext wired to the current BattleSceneContext.
- /// ApplyDamage clamps at 0 (no negative health from defence overflow),
- /// EndBattle delegates to ShowWinground/ShowDeadground on the scene
- /// context. ScheduleAnimation and EndTurn are stubs — specific battle
- /// cores are expected to override or supplement this behaviour.
- ///
- public class BattleContextImpl : IBattleContext
- {
- private readonly BattleSceneContext _sceneContext;
-
- ///
- /// Root Godot of the battle scene, used by
- /// animation actions to parent spawned visual effects.
- ///
- public Node RootNode => _sceneContext;
-
- ///
- /// The owning that manages
- /// the battle lifecycle.
- ///
- public BattleSceneContext SceneContext => _sceneContext;
-
- ///
- /// Creates a context adapter around the given scene context.
- ///
- public BattleContextImpl(BattleSceneContext sceneContext)
- {
- _sceneContext = sceneContext;
- }
-
- ///
- /// Subtracts from
- /// , clamping at 0 to prevent negative
- /// health from defence overflow, and applies the result to
- /// 's health.
- ///
- public void ApplyDamage(Character source, Character target, int rawDamage, int defence)
- {
- var finalDamage = Mathf.Max(0, rawDamage - defence);
- target.Attributes.Health.Value -= finalDamage;
- }
-
- ///
- /// Stub — specific battle cores override this to enqueue
- /// animation actions into the .
- ///
- public void ScheduleAnimation(IBattleAnimation animation)
- {
- }
-
- ///
- /// Stub — cores implement this to advance the turn sequence
- /// when a side finishes its action phase.
- ///
- public void EndTurn(BattleSide side)
- {
- }
-
- ///
- /// Resolves the battle outcome by delegating to
- /// for player
- /// victory or
- /// for enemy victory.
- ///
- public void EndBattle(BattleSide winner)
- {
- if (winner == BattleSide.Player)
- _sceneContext.ShowWinground();
- else
- _sceneContext.ShowDeadground();
- }
- }
-}
+using Cthangover.Core.Characters;
+using Godot;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Default IBattleContext wired to the current BattleSceneContext.
+ /// ApplyDamage clamps at 0 (no negative health from defence overflow),
+ /// EndBattle delegates to ShowWinground/ShowDeadground on the scene
+ /// context. ScheduleAnimation and EndTurn are stubs — specific battle
+ /// cores are expected to override or supplement this behaviour.
+ ///
+ public class BattleContextImpl : IBattleContext
+ {
+ private readonly BattleSceneContext _sceneContext;
+
+ ///
+ /// Root Godot of the battle scene, used by
+ /// animation actions to parent spawned visual effects.
+ ///
+ public Node RootNode => _sceneContext;
+
+ ///
+ /// The owning that manages
+ /// the battle lifecycle.
+ ///
+ public BattleSceneContext SceneContext => _sceneContext;
+
+ ///
+ /// Creates a context adapter around the given scene context.
+ ///
+ public BattleContextImpl(BattleSceneContext sceneContext)
+ {
+ _sceneContext = sceneContext;
+ }
+
+ ///
+ /// Subtracts from
+ /// , clamping at 0 to prevent negative
+ /// health from defence overflow, and applies the result to
+ /// 's health.
+ ///
+ public void ApplyDamage(Character source, Character target, int rawDamage, int defence)
+ {
+ var finalDamage = Mathf.Max(0, rawDamage - defence);
+ target.Attributes.Health.Value -= finalDamage;
+ }
+
+ ///
+ /// Stub — specific battle cores override this to enqueue
+ /// animation actions into the .
+ ///
+ public void ScheduleAnimation(IBattleAnimation animation)
+ {
+ }
+
+ ///
+ /// Stub — cores implement this to advance the turn sequence
+ /// when a side finishes its action phase.
+ ///
+ public void EndTurn(BattleSide side)
+ {
+ }
+
+ ///
+ /// Resolves the battle outcome by delegating to
+ /// for player
+ /// victory or
+ /// for enemy victory.
+ ///
+ public void EndBattle(BattleSide winner)
+ {
+ if (winner == BattleSide.Player)
+ _sceneContext.ShowWinground();
+ else
+ _sceneContext.ShowDeadground();
+ }
+ }
+}
diff --git a/src/Adapters/IBattleAnimation.cs b/src/Adapters/IBattleAnimation.cs
index 7771549..aa38d21 100644
--- a/src/Adapters/IBattleAnimation.cs
+++ b/src/Adapters/IBattleAnimation.cs
@@ -1,33 +1,33 @@
-using System.Collections.Generic;
-using Cthangover.Core.Characters;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Describes a battle animation without binding to a concrete player.
- /// Carries Source/Target characters, an AnimationType key for dispatch,
- /// a Speed multiplier, and an open-ended Parameters dictionary so
- /// custom animations can pass arbitrary data (damage value, element
- /// type, etc.) without interface changes.
- ///
- public interface IBattleAnimation
- {
- /// The performing the action.
- Character Source { get; }
- /// The receiving the action.
- Character Target { get; }
- ///
- /// Key used by to dispatch to the
- /// correct .
- ///
- string AnimationType { get; }
- /// Multiplier for the playback speed (1.0 = normal).
- float Speed { get; }
- ///
- /// Open-ended data bag for animation-specific values
- /// (e.g. damage amount, elemental flag) — no interface changes
- /// needed when adding new animation types.
- ///
- Dictionary Parameters { get; }
- }
-}
+using System.Collections.Generic;
+using Cthangover.Core.Characters;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Describes a battle animation without binding to a concrete player.
+ /// Carries Source/Target characters, an AnimationType key for dispatch,
+ /// a Speed multiplier, and an open-ended Parameters dictionary so
+ /// custom animations can pass arbitrary data (damage value, element
+ /// type, etc.) without interface changes.
+ ///
+ public interface IBattleAnimation
+ {
+ /// The performing the action.
+ Character Source { get; }
+ /// The receiving the action.
+ Character Target { get; }
+ ///
+ /// Key used by to dispatch to the
+ /// correct .
+ ///
+ string AnimationType { get; }
+ /// Multiplier for the playback speed (1.0 = normal).
+ float Speed { get; }
+ ///
+ /// Open-ended data bag for animation-specific values
+ /// (e.g. damage amount, elemental flag) — no interface changes
+ /// needed when adding new animation types.
+ ///
+ Dictionary Parameters { get; }
+ }
+}
diff --git a/src/Adapters/IBattleAnimationPlayer.cs b/src/Adapters/IBattleAnimationPlayer.cs
index 73b947c..d903399 100644
--- a/src/Adapters/IBattleAnimationPlayer.cs
+++ b/src/Adapters/IBattleAnimationPlayer.cs
@@ -1,26 +1,26 @@
-using System;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Plays a specific animation type. Each player is keyed by
- /// AnimationType so the registry can dispatch animations without
- /// knowing concrete player types. The onComplete callback chains
- /// into the action machine's polling loop.
- ///
- public interface IBattleAnimationPlayer
- {
- ///
- /// String key used by to select
- /// this player for a given .
- ///
- string AnimationType { get; }
-
- ///
- /// Starts playing the animation described by .
- /// Must invoke once the animation
- /// finishes, so the action machine can advance to the next step.
- ///
- void Play(IBattleAnimation anim, Action onComplete);
- }
-}
+using System;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Plays a specific animation type. Each player is keyed by
+ /// AnimationType so the registry can dispatch animations without
+ /// knowing concrete player types. The onComplete callback chains
+ /// into the action machine's polling loop.
+ ///
+ public interface IBattleAnimationPlayer
+ {
+ ///
+ /// String key used by to select
+ /// this player for a given .
+ ///
+ string AnimationType { get; }
+
+ ///
+ /// Starts playing the animation described by .
+ /// Must invoke once the animation
+ /// finishes, so the action machine can advance to the next step.
+ ///
+ void Play(IBattleAnimation anim, Action onComplete);
+ }
+}
diff --git a/src/Adapters/IBattleContext.cs b/src/Adapters/IBattleContext.cs
index b7e340d..2d0191c 100644
--- a/src/Adapters/IBattleContext.cs
+++ b/src/Adapters/IBattleContext.cs
@@ -1,27 +1,27 @@
-using Cthangover.Core.Characters;
-using Godot;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Bridge from the battle core back to the Godot scene. Cores work
- /// against this interface so they remain engine-agnostic.
- /// ApplyDamage computes final-damage-minus-defence and mutates
- /// the target's Health directly (no event — the UI layer polls).
- /// ScheduleAnimation is the hook for visual feedback; EndTurn/EndBattle
- /// are phase signals. RootNode gives cores a Godot anchor for
- /// instantiating nodes (e.g. damage numbers).
- ///
- public interface IBattleContext
- {
- void ApplyDamage(Character source, Character target, int rawDamage, int defence);
-
- void ScheduleAnimation(IBattleAnimation animation);
-
- void EndTurn(BattleSide side);
-
- void EndBattle(BattleSide winner);
-
- Node RootNode { get; }
- }
-}
+using Cthangover.Core.Characters;
+using Godot;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Bridge from the battle core back to the Godot scene. Cores work
+ /// against this interface so they remain engine-agnostic.
+ /// ApplyDamage computes final-damage-minus-defence and mutates
+ /// the target's Health directly (no event — the UI layer polls).
+ /// ScheduleAnimation is the hook for visual feedback; EndTurn/EndBattle
+ /// are phase signals. RootNode gives cores a Godot anchor for
+ /// instantiating nodes (e.g. damage numbers).
+ ///
+ public interface IBattleContext
+ {
+ void ApplyDamage(Character source, Character target, int rawDamage, int defence);
+
+ void ScheduleAnimation(IBattleAnimation animation);
+
+ void EndTurn(BattleSide side);
+
+ void EndBattle(BattleSide winner);
+
+ Node RootNode { get; }
+ }
+}
diff --git a/src/Adapters/IBattleCore.cs b/src/Adapters/IBattleCore.cs
index f5522ce..53a62bc 100644
--- a/src/Adapters/IBattleCore.cs
+++ b/src/Adapters/IBattleCore.cs
@@ -1,46 +1,46 @@
-using Cthangover.Core.Battle.Actions;
-using Cthangover.Core.Characters;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Pluggable battle engine contract. Each implementation is a
- /// self-contained battle rule set (turn order, AI, phases).
- /// Init receives the two character arrays and a context adapter;
- /// Start begins the loop. The ActionProvider property lets the
- /// core inject its own executor overrides via the hub.
- /// Cores are instantiated fresh per battle — they carry no
- /// persistent state between encounters.
- ///
- public interface IBattleCore
- {
- ///
- /// Unique identifier for this battle engine (e.g. "card_battle",
- /// "ff_battle"). Used by and
- /// for lookup.
- ///
- string Id { get; }
-
- ///
- /// Provider that returns core-specific
- /// overrides. Set on the when
- /// this core activates, so actions route through custom logic
- /// before falling back to the global registry.
- ///
- IActionExecutorProvider ActionProvider { get; }
-
- ///
- /// Initialises the core with the two character arrays and a
- /// for callbacks. Called once per
- /// battle before .
- ///
- void Init(Character[] playerChars, Character[] enemyChars, IBattleContext ctx);
-
- ///
- /// Begins the turn loop. Cores may use coroutines, polling, or
- /// event-driven state machines — the core owns its own lifecycle
- /// after this call.
- ///
- void Start();
- }
-}
+using Cthangover.Core.Battle.Actions;
+using Cthangover.Core.Characters;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Pluggable battle engine contract. Each implementation is a
+ /// self-contained battle rule set (turn order, AI, phases).
+ /// Init receives the two character arrays and a context adapter;
+ /// Start begins the loop. The ActionProvider property lets the
+ /// core inject its own executor overrides via the hub.
+ /// Cores are instantiated fresh per battle — they carry no
+ /// persistent state between encounters.
+ ///
+ public interface IBattleCore
+ {
+ ///
+ /// Unique identifier for this battle engine (e.g. "card_battle",
+ /// "ff_battle"). Used by and
+ /// for lookup.
+ ///
+ string Id { get; }
+
+ ///
+ /// Provider that returns core-specific
+ /// overrides. Set on the when
+ /// this core activates, so actions route through custom logic
+ /// before falling back to the global registry.
+ ///
+ IActionExecutorProvider ActionProvider { get; }
+
+ ///
+ /// Initialises the core with the two character arrays and a
+ /// for callbacks. Called once per
+ /// battle before .
+ ///
+ void Init(Character[] playerChars, Character[] enemyChars, IBattleContext ctx);
+
+ ///
+ /// Begins the turn loop. Cores may use coroutines, polling, or
+ /// event-driven state machines — the core owns its own lifecycle
+ /// after this call.
+ ///
+ void Start();
+ }
+}
diff --git a/src/Adapters/IBattleSide.cs b/src/Adapters/IBattleSide.cs
index 11520b4..e92f70e 100644
--- a/src/Adapters/IBattleSide.cs
+++ b/src/Adapters/IBattleSide.cs
@@ -1,15 +1,15 @@
-namespace Cthangover.Core.Battle
-{
- ///
- /// Identifies which side of the battle a character or turn belongs to.
- /// Used by IBattleContext.EndTurn and EndBattle to route results
- /// (victory vs defeat screen).
- ///
- public enum BattleSide
- {
- /// The player's party.
- Player,
- /// The opposing force (monsters, NPCs).
- Enemy,
- }
-}
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Identifies which side of the battle a character or turn belongs to.
+ /// Used by IBattleContext.EndTurn and EndBattle to route results
+ /// (victory vs defeat screen).
+ ///
+ public enum BattleSide
+ {
+ /// The player's party.
+ Player,
+ /// The opposing force (monsters, NPCs).
+ Enemy,
+ }
+}
diff --git a/src/Base/ActionBase.cs b/src/Base/ActionBase.cs
index dbd0c47..b6f5557 100644
--- a/src/Base/ActionBase.cs
+++ b/src/Base/ActionBase.cs
@@ -1,62 +1,62 @@
-
-using Cthangover.Core.Battle.Actions;
-using Cthangover.Core.Utils;
-
-namespace Cthangover.Core.Characters
-{
-
- ///
- /// Convenience base for IActionExecutor implementations. Provides
- /// CheckRequiredAndUsePoint which validates that the user has enough
- /// "Point" attribute (a generic action-point / mana pool) before
- /// deducting it — returning false lets the action fail gracefully.
- /// ID is abstract so each action declares its own identity.
- ///
- public abstract class ActionBase : IActionExecutor
- {
-
- ///
- /// Maps to the abstract so the hub can resolve
- /// this executor by action name.
- ///
- public string ActionId => ID;
-
- ///
- /// Unique action identifier that matches
- /// in data.
- ///
- public abstract string ID { get; }
- ///
- /// Applies the action's effects to
- /// using 's stats and the
- /// definition. Returns stat deltas
- /// and a success/failure flag.
- ///
- public abstract ChangedAttributes Execute(ActionCharacter action, Character user, Character target);
-
- ///
- /// Validates that has at least
- /// "RequiredPoint" remaining, deducts it, and
- /// returns true. Returns false with a warning log
- /// if the user lacks sufficient points, allowing the action to
- /// fail gracefully.
- ///
- protected bool CheckRequiredAndUsePoint(ActionCharacter action, Character user)
- {
- var requiredPoint = action.GetInt("RequiredPoint");
-
- if (user.Attributes.Point.Value < requiredPoint)
- {
- GameLogger.Log("BATTLE", user.ID + " not checked point required [" + user.Attributes.Point.Value + "] for action " + action.ID + " [" + requiredPoint + "]", LogLevel.Warning);
- return false;
- }
-
- GameLogger.Log("BATTLE", user.ID + " use [" + requiredPoint + "] points for action " + action.ID, LogLevel.Debug);
-
- user.Attributes.Point.Value -= requiredPoint;
- return true;
- }
-
- }
-
-}
+
+using Cthangover.Core.Battle.Actions;
+using Cthangover.Core.Utils;
+
+namespace Cthangover.Core.Characters
+{
+
+ ///
+ /// Convenience base for IActionExecutor implementations. Provides
+ /// CheckRequiredAndUsePoint which validates that the user has enough
+ /// "Point" attribute (a generic action-point / mana pool) before
+ /// deducting it — returning false lets the action fail gracefully.
+ /// ID is abstract so each action declares its own identity.
+ ///
+ public abstract class ActionBase : IActionExecutor
+ {
+
+ ///
+ /// Maps to the abstract so the hub can resolve
+ /// this executor by action name.
+ ///
+ public string ActionId => ID;
+
+ ///
+ /// Unique action identifier that matches
+ /// in data.
+ ///
+ public abstract string ID { get; }
+ ///
+ /// Applies the action's effects to
+ /// using 's stats and the
+ /// definition. Returns stat deltas
+ /// and a success/failure flag.
+ ///
+ public abstract ChangedAttributes Execute(ActionCharacter action, Character user, Character target);
+
+ ///
+ /// Validates that has at least
+ /// "RequiredPoint" remaining, deducts it, and
+ /// returns true. Returns false with a warning log
+ /// if the user lacks sufficient points, allowing the action to
+ /// fail gracefully.
+ ///
+ protected bool CheckRequiredAndUsePoint(ActionCharacter action, Character user)
+ {
+ var requiredPoint = action.GetInt("RequiredPoint");
+
+ if (user.Attributes.Point.Value < requiredPoint)
+ {
+ GameLogger.Log("BATTLE", user.ID + " not checked point required [" + user.Attributes.Point.Value + "] for action " + action.ID + " [" + requiredPoint + "]", LogLevel.Warning);
+ return false;
+ }
+
+ GameLogger.Log("BATTLE", user.ID + " use [" + requiredPoint + "] points for action " + action.ID, LogLevel.Debug);
+
+ user.Attributes.Point.Value -= requiredPoint;
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/BattleData.cs b/src/BattleData.cs
index 2a451bc..acf1c98 100644
--- a/src/BattleData.cs
+++ b/src/BattleData.cs
@@ -1,98 +1,98 @@
-using System.Collections.Generic;
-using System.Linq;
-using Cthangover.Core.Battle;
-using Cthangover.Core.Factories.Impls;
-using Cthangover.Core.Quests;
-using Cthangover.Core.Characters;
-using Godot;
-
-namespace Cthangover.Core.Settings
-{
- ///
- /// Immutable descriptor for a battle encounter, constructed via the
- /// static factory overloads. Holds the
- /// background texture, enemy roster, quest reference, card order side,
- /// and the scene to return to when the battle ends. The private
- /// constructor forces creation through the factories so that all
- /// required data is supplied upfront.
- ///
- public class BattleData
- {
- /// The quest that triggered this battle; may be null
- /// for non-quest skirmishes.
- public QuestBase Quest { get; set; }
- /// Auxiliary tag set on new characters acquired via recruitment
- /// after this battle.
- public string NewTag { get; set; }
-
- /// List of enemy instances spawned from
- /// the provided IDs or default templates.
- public List EnemiesCards { get; private set; }
- /// Battle background texture (full-resolution).
- public Texture2D Background { get; private set; }
- /// Depth map texture for the parallax post-process effect.
- public Texture2D DepthMap { get; set; }
- /// Albedo/colour map texture for the parallax post-process effect.
- public Texture2D AlbedoMap { get; set; }
- /// Which side of the screen the player's cards are drawn on.
- public BattleSide BattleSide { get; private set; } = BattleSide.Player;
- /// Identifier for the active battle core logic module.
- public string ActiveBattleCore { get; set; }
- /// Scene name to transition to when the battle finishes or is
- /// fled. Stored as a string to avoid type coupling.
- public string ReturnScene { get; private set; }
-
- private BattleData()
- { }
-
- ///
- /// Convenience overload that defaults to
- /// BattleSide.Player. Constructs enemies from their string IDs
- /// via ,
- /// falling back to a level-1 template when the ID is unknown.
- ///
- public static BattleData InitBattle(Texture2D background, string returnScene, params string[] enemies)
- {
- return InitBattle(background, returnScene, BattleSide.Player, enemies);
- }
-
- ///
- /// Creates a battle with a specified and
- /// enemy IDs. Each ID is resolved through
- /// .
- /// Unknown IDs produce a fallback with 20 HP
- /// and 3 action points.
- ///
- public static BattleData InitBattle(Texture2D background, string returnScene, BattleSide cardOrder = BattleSide.Player, params string[] enemies)
- {
- var list = enemies.Select(o => CharacterFactory.Instance.Get(o) ?? new Character
- {
- ID = o,
- Attributes = new CharacterAttributes
- {
- Health = new Attribute { Value = 20, BaseValue = 20 },
- Point = new Attribute { Value = 3, BaseValue = 3 },
- },
- Level = 1,
- Exp = 10,
- }).ToList();
- return InitBattle(background, returnScene, list, cardOrder);
- }
-
- ///
- /// Core factory that directly accepts a pre-built
- /// for the enemy roster. This is the
- /// overload that all other InitBattle variants delegate to.
- ///
- public static BattleData InitBattle(Texture2D background, string returnScene, List enemiesCards, BattleSide cardOrder = BattleSide.Player)
- {
- return new BattleData
- {
- Background = background,
- ReturnScene = returnScene,
- EnemiesCards = enemiesCards,
- BattleSide = cardOrder,
- };
- }
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using Cthangover.Core.Battle;
+using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.Quests;
+using Cthangover.Core.Characters;
+using Godot;
+
+namespace Cthangover.Core.Settings
+{
+ ///
+ /// Immutable descriptor for a battle encounter, constructed via the
+ /// static factory overloads. Holds the
+ /// background texture, enemy roster, quest reference, card order side,
+ /// and the scene to return to when the battle ends. The private
+ /// constructor forces creation through the factories so that all
+ /// required data is supplied upfront.
+ ///
+ public class BattleData
+ {
+ /// The quest that triggered this battle; may be null
+ /// for non-quest skirmishes.
+ public QuestBase Quest { get; set; }
+ /// Auxiliary tag set on new characters acquired via recruitment
+ /// after this battle.
+ public string NewTag { get; set; }
+
+ /// List of enemy instances spawned from
+ /// the provided IDs or default templates.
+ public List EnemiesCards { get; private set; }
+ /// Battle background texture (full-resolution).
+ public Texture2D Background { get; private set; }
+ /// Depth map texture for the parallax post-process effect.
+ public Texture2D DepthMap { get; set; }
+ /// Albedo/colour map texture for the parallax post-process effect.
+ public Texture2D AlbedoMap { get; set; }
+ /// Which side of the screen the player's cards are drawn on.
+ public BattleSide BattleSide { get; private set; } = BattleSide.Player;
+ /// Identifier for the active battle core logic module.
+ public string ActiveBattleCore { get; set; }
+ /// Scene name to transition to when the battle finishes or is
+ /// fled. Stored as a string to avoid type coupling.
+ public string ReturnScene { get; private set; }
+
+ private BattleData()
+ { }
+
+ ///
+ /// Convenience overload that defaults to
+ /// BattleSide.Player. Constructs enemies from their string IDs
+ /// via ,
+ /// falling back to a level-1 template when the ID is unknown.
+ ///
+ public static BattleData InitBattle(Texture2D background, string returnScene, params string[] enemies)
+ {
+ return InitBattle(background, returnScene, BattleSide.Player, enemies);
+ }
+
+ ///
+ /// Creates a battle with a specified and
+ /// enemy IDs. Each ID is resolved through
+ /// .
+ /// Unknown IDs produce a fallback with 20 HP
+ /// and 3 action points.
+ ///
+ public static BattleData InitBattle(Texture2D background, string returnScene, BattleSide cardOrder = BattleSide.Player, params string[] enemies)
+ {
+ var list = enemies.Select(o => CharacterFactory.Instance.Get(o) ?? new Character
+ {
+ ID = o,
+ Attributes = new CharacterAttributes
+ {
+ Health = new Attribute { Value = 20, BaseValue = 20 },
+ Point = new Attribute { Value = 3, BaseValue = 3 },
+ },
+ Level = 1,
+ Exp = 10,
+ }).ToList();
+ return InitBattle(background, returnScene, list, cardOrder);
+ }
+
+ ///
+ /// Core factory that directly accepts a pre-built
+ /// for the enemy roster. This is the
+ /// overload that all other InitBattle variants delegate to.
+ ///
+ public static BattleData InitBattle(Texture2D background, string returnScene, List enemiesCards, BattleSide cardOrder = BattleSide.Player)
+ {
+ return new BattleData
+ {
+ Background = background,
+ ReturnScene = returnScene,
+ EnemiesCards = enemiesCards,
+ BattleSide = cardOrder,
+ };
+ }
+ }
+}
diff --git a/src/BattleSceneContext.cs b/src/BattleSceneContext.cs
index 1f789fc..b94c96c 100644
--- a/src/BattleSceneContext.cs
+++ b/src/BattleSceneContext.cs
@@ -1,368 +1,368 @@
-using System.Collections.Generic;
-using System.Linq;
-using Cthangover.Core.Events;
-using Cthangover.Core.Factories.Impls;
-using Cthangover.Core.Mods;
-using Cthangover.Core.Settings;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Time;
-using Cthangover.Core.Characters;
-using Cthangover.Core.Mods.Resolvers;
-using Cthangover.Core.Utils;
-using Godot;
-
+using System.Collections.Generic;
+using System.Linq;
+using Cthangover.Core.Events;
+using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.Mods;
+using Cthangover.Core.Settings;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Time;
+using Cthangover.Core.Characters;
+using Cthangover.Core.Mods.Resolvers;
+using Cthangover.Core.Utils;
+using Godot;
+
using Cthangover.Mods.Interface.Base;
using Cthangover.Mods.Interface.Base.Lists;
-namespace Cthangover.Core.Battle
-{
- ///
- /// Singleton scene-root for battle, wired as a self-registering Node
- /// (sets Instance on enter-tree, clears on exit-tree). On ready it
- /// assembles player characters from CharacterData.BattleSet, enemy
- /// cards from the pending , resolves the active
- /// via , and defers
- /// Init+Start to the next frame so the scene tree is fully built before core
- /// logic runs. Tracks PlayerExpGained (per-character, split evenly among
- /// survivors) and DefeatedEnemies for the victory/loot screen. Exposes
- /// OnCharacterDied and OnBattleCleared events so external systems (e.g.
- /// quests, the action machine) can react without coupling.
- /// ShowWinground / ShowDeadground are mutually exclusive end-of-battle
- /// paths — both set IsDestroyed to prevent double-triggering and
- /// reset battle speed before clearing state.
- ///
- /// The context derives from because mod assemblies get
- /// no Godot source generation: the core base receives the engine lifecycle
- /// callbacks and forwards them to the OnXxx overrides. It also
- /// implements so core UI (the inventory
- /// panel's action-point row) can read the acting character without knowing
- /// anything about battle.
- ///
- public partial class BattleSceneContext : ModNode, IActiveCharacterProvider
- {
- ///
- /// Singleton reference to the active battle scene context.
- /// Set on enter-tree, cleared on exit-tree.
- /// External systems read this to access battle state.
- ///
- public static BattleSceneContext Instance { get; private set; }
-
- private TextureRect battleBg;
-
- ///
- /// The node that
- /// processes the animation queue for this battle. Cores push
- /// animation actions through the context and the machine drives
- /// playback frame-by-frame.
- ///
- public Actions.BattleActionMachine BattleActionMachine { get; private set; }
-
- private IBattleSpeedControl _battleSpeedControl;
- private Widget deadground;
- private Widget winground;
-
- ///
- /// Per-character EXP earned during this battle, keyed by
- /// . Populated by
- /// ; consumed by the victory screen
- /// provided by the turn-battle GUI mod.
- ///
- public Dictionary PlayerExpGained { get; private set; } = new();
- ///
- /// Enemies defeated during this battle (for loot generation
- /// and recruitment rolls).
- ///
- public List DefeatedEnemies { get; private set; } = new();
-
- ///
- /// Guards against double-triggering end-of-battle paths.
- /// Set to true by or
- /// .
- ///
- public bool IsDestroyed { get; private set; }
-
- ///
- /// When true, the battle core pauses processing (e.g.
- /// while a UI overlay is open). Set externally by pause widgets.
- ///
- public bool IsWait { get; set; }
-
- ///
- /// The whose turn is currently active
- /// in battle. Set by the battle core when a character is selected
- /// and cleared when the turn ends or selection is cancelled.
- /// null means no character is active (out of battle,
- /// between turns, or idle state). External panels such as the
- /// inventory panel read this to check whether the active
- /// character has enough action points before performing
- /// item-related actions.
- ///
- public Character ActiveCharacter { get; set; }
-
- /// Fired when a character's health reaches 0.
- public event System.Action OnCharacterDied;
- ///
- /// Fired after battle state is cleared (both victory and defeat).
- /// External systems can hook this to release battle resources.
- ///
- public event System.Action OnBattleCleared;
-
- ///
- /// Invokes to notify listeners
- /// (quests, achievements, the battle core) that a character
- /// has been defeated.
- ///
- public void NotifyCharacterDied(Character character)
- {
- OnCharacterDied?.Invoke(character);
- ModEventBus.Publish(new CharacterDiedEvent
- {
- CharacterId = character?.ID,
- CharacterName = character?.Name,
- Source = "battle",
- });
- }
-
- /// Claims the scene singleton and registers as the provider.
- protected override void OnEnterTree()
- {
- if (Instance != null && GodotObject.IsInstanceValid(Instance))
- {
- var scene = GetTree()?.CurrentScene?.Name ?? "?";
- var existingPath = Instance.GetPath().ToString();
- var myPath = GetPath().ToString();
- GameLogger.Log("DUPLICATE", $"BattleSceneContext.OnEnterTree: Instance ALREADY SET by '{existingPath}', overwriting with duplicate at '{myPath}' on scene '{scene}'", LogLevel.Error);
- }
- Instance = this;
- ActiveCharacterRegistry.Provider = this;
- }
-
- /// Releases the singleton slot and the active-character provider so a later scene can claim them.
- protected override void OnExitTree()
- {
- base.OnExitTree();
- if (Instance == this)
- Instance = null;
- if (ReferenceEquals(ActiveCharacterRegistry.Provider, this))
- ActiveCharacterRegistry.Provider = null;
- }
-
- private List pendingPlayerCards;
- private List pendingEnemyCards;
- private IBattleCore activeBattleCore;
-
- ///
- /// The battle core driving this scene, resolved from
- /// when the scene becomes ready. Exposed
- /// so tooling and tests can query the active core (for example for
- /// ) without reaching into UI nodes.
- ///
- public IBattleCore ActiveBattleCore => activeBattleCore;
-
- /// Caches the battle backdrop node, resolves the active and starts the battle.
- protected override void OnReady()
- {
- battleBg = GetNodeOrNull("BattleBg");
-
- if (battleBg == null)
- {
- GameLogger.Log("BATTLE", "battleBg not found!", LogLevel.Error);
- return;
- }
-
- BattleActionMachine = GetNodeOrNull("ToolPanel/BattleActionMachine");
- _battleSpeedControl = GetNodeOrNull("BattleSpeedControl") as IBattleSpeedControl;
- deadground = GetNodeOrNull("Deadground");
-
- winground = GetNodeOrNull("Winground");
-
- var data = BattleRequest.Current;
- if (data == null)
- {
- GameLogger.Log("BATTLE", "BattleRequest.Current is null!", LogLevel.Error);
- return;
- }
-
- GameLogger.Log("BATTLE", $"set battle background: battleBg={(battleBg != null ? "found" : "NULL")}, texture={(data.Background != null ? "has" : "NULL")}", battleBg == null || data.Background == null ? LogLevel.Error : LogLevel.Debug);
-
- if (data.Background != null)
- {
- battleBg.Texture = data.Background;
- GameLogger.Log("BATTLE", $" battleBg.Texture assigned: width={battleBg.Texture.GetWidth()}, height={battleBg.Texture.GetHeight()}", LogLevel.Debug);
- }
- else
- {
- GameLogger.Log("BATTLE", $" data.Background is NULL — no background texture set", LogLevel.Error);
- }
-
- var timedShader = Shaders.Resolve("timed_sprite");
- if (timedShader != null)
- {
- var bgMaterial = new ShaderMaterial { Shader = timedShader };
- battleBg.Material = bgMaterial;
-
- if (data.DepthMap != null)
- bgMaterial.SetShaderParameter("depth_mask", data.DepthMap);
- if (data.AlbedoMap != null)
- bgMaterial.SetShaderParameter("albedo_map", data.AlbedoMap);
-
- var lights = GetNodeOrNull("Lights");
- if (lights != null)
- {
- var viewportSize = GetViewport().GetVisibleRect().Size;
- lights.SetPlayerLight(viewportSize / 2, 500f, 1f);
- lights.RegisterMaterial(bgMaterial);
- }
- GameLogger.Log("BATTLE", $"battleBg shader: timed_sprite applied, depthMap={(data.DepthMap != null ? "has" : "NULL")}, albedoMap={(data.AlbedoMap != null ? "has" : "NULL")}, lightsCtrl={(lights != null ? "found" : "NULL")}", lights == null ? LogLevel.Error : LogLevel.Debug);
- }
- else
- {
- GameLogger.Log("BATTLE", "battleBg shader: timed_sprite NOT FOUND", LogLevel.Error);
- }
-
- pendingPlayerCards = new List();
- foreach (var characterType in GameData.Instance.Runtime.CharacterData.BattleSet)
- {
- var characterInfo = GameData.Instance.Runtime.CharacterData.Characters[characterType];
- var template = CharacterFactory.Instance.Get(characterInfo.ID);
- var character = template != null ? template.Copy() : new Character { ID = characterInfo.ID };
- character.Attributes = characterInfo.Attributes ?? new CharacterAttributes();
- character.Level = characterInfo.Level;
- character.Exp = characterInfo.Exp;
- if (characterInfo.ActionSlots != null && characterInfo.ActionSlots.Any(id => !string.IsNullOrEmpty(id)))
- {
- character.Actions = characterInfo.ActionSlots
- .Where(id => !string.IsNullOrEmpty(id))
- .Select(id => ActionCharacterFactory.Instance.Get(id))
- .Where(a => a != null)
- .ToList();
- }
- pendingPlayerCards.Add(character);
- }
-
- pendingEnemyCards = data.EnemiesCards?.Where(c => c != null).ToList() ?? new List();
-
- if (!string.IsNullOrEmpty(data.ActiveBattleCore) && BattleCoreRegistry.Instance.HasCore(data.ActiveBattleCore))
- BattleCoreRegistry.Instance.SetActive(data.ActiveBattleCore);
-
- try
- {
- activeBattleCore = BattleCoreRegistry.Instance.GetActive();
- }
- catch
- {
- activeBattleCore = null;
- }
-
- if (activeBattleCore == null)
- {
- GameLogger.Log("BATTLE", "No battle core available", LogLevel.Error);
- return;
- }
-
- Callable.From(DeferredInit).CallDeferred();
- }
-
- private void DeferredInit()
- {
- if (IsDestroyed || activeBattleCore == null)
- return;
-
- var ctx = new BattleContextImpl(this);
- var playerArray = pendingPlayerCards?.ToArray() ?? new Character[0];
- var enemyArray = pendingEnemyCards?.ToArray() ?? new Character[0];
-
- activeBattleCore.Init(playerArray, enemyArray, ctx);
- activeBattleCore.Start();
-
- ModEventBus.Publish(new BattleStartedEvent
- {
- BattleType = activeBattleCore.GetType().Name,
- Enemies = enemyArray.Select(c => c.Name).ToArray(),
- });
- }
-
- ///
- /// Triggers the defeat screen: sets ,
- /// resets battle speed, clears battle state, and shows the
- /// deadground widget.
- ///
- public void ShowDeadground()
- {
- if (IsDestroyed)
- return;
- IsDestroyed = true;
- _battleSpeedControl?.ResetToNormal();
- ClearBattle("defeat");
- deadground?.Show();
- }
-
- ///
- /// Triggers the victory screen: sets ,
- /// resets battle speed, clears battle state, and shows the
- /// winground widget.
- ///
- public void ShowWinground()
- {
- if (IsDestroyed)
- return;
- IsDestroyed = true;
- _battleSpeedControl?.ResetToNormal();
- ClearBattle("victory");
- winground?.Show();
- }
-
- ///
- /// Records a defeated enemy: adds it to
- /// and distributes its EXP
- /// (Level * Exp) equally among all surviving player
- /// characters in .
- ///
- public void RecordEnemyDefeated(Character enemy)
- {
- if (enemy == null)
- return;
- DefeatedEnemies.Add(enemy);
-
- var enemyExp = enemy.Level * enemy.Exp;
- var alivePlayers = 0;
- foreach (var c in pendingPlayerCards)
- {
- if (c?.Attributes?.Health?.Value > 0)
- alivePlayers++;
- }
- if (alivePlayers == 0)
- return;
-
- var expPerPlayer = enemyExp / alivePlayers;
- foreach (var c in pendingPlayerCards)
- {
- if (c?.Attributes?.Health?.Value <= 0)
- continue;
- var id = c.ID;
- if (id == null)
- continue;
- PlayerExpGained.TryGetValue(id, out var current);
- PlayerExpGained[id] = current + expPerPlayer;
- }
- }
-
- ///
- /// Stops the battle and publishes . The
- /// outcome is passed in by the caller: both screens set
- /// before clearing, so it cannot be derived
- /// from that flag (it used to be, and every battle reported "defeat").
- ///
- private void ClearBattle(string result)
- {
- BattleActionMachine?.StopMachine();
- ActiveCharacter = null;
- pendingPlayerCards?.Clear();
- pendingEnemyCards?.Clear();
- OnBattleCleared?.Invoke();
- ModEventBus.Publish(new BattleEndedEvent
- {
- Result = result,
- });
- }
- }
-}
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Singleton scene-root for battle, wired as a self-registering Node
+ /// (sets Instance on enter-tree, clears on exit-tree). On ready it
+ /// assembles player characters from CharacterData.BattleSet, enemy
+ /// cards from the pending , resolves the active
+ /// via , and defers
+ /// Init+Start to the next frame so the scene tree is fully built before core
+ /// logic runs. Tracks PlayerExpGained (per-character, split evenly among
+ /// survivors) and DefeatedEnemies for the victory/loot screen. Exposes
+ /// OnCharacterDied and OnBattleCleared events so external systems (e.g.
+ /// quests, the action machine) can react without coupling.
+ /// ShowWinground / ShowDeadground are mutually exclusive end-of-battle
+ /// paths — both set IsDestroyed to prevent double-triggering and
+ /// reset battle speed before clearing state.
+ ///
+ /// The context derives from because mod assemblies get
+ /// no Godot source generation: the core base receives the engine lifecycle
+ /// callbacks and forwards them to the OnXxx overrides. It also
+ /// implements so core UI (the inventory
+ /// panel's action-point row) can read the acting character without knowing
+ /// anything about battle.
+ ///
+ public partial class BattleSceneContext : ModNode, IActiveCharacterProvider
+ {
+ ///
+ /// Singleton reference to the active battle scene context.
+ /// Set on enter-tree, cleared on exit-tree.
+ /// External systems read this to access battle state.
+ ///
+ public static BattleSceneContext Instance { get; private set; }
+
+ private TextureRect battleBg;
+
+ ///
+ /// The node that
+ /// processes the animation queue for this battle. Cores push
+ /// animation actions through the context and the machine drives
+ /// playback frame-by-frame.
+ ///
+ public Actions.BattleActionMachine BattleActionMachine { get; private set; }
+
+ private IBattleSpeedControl _battleSpeedControl;
+ private Widget deadground;
+ private Widget winground;
+
+ ///
+ /// Per-character EXP earned during this battle, keyed by
+ /// . Populated by
+ /// ; consumed by the victory screen
+ /// provided by the turn-battle GUI mod.
+ ///
+ public Dictionary PlayerExpGained { get; private set; } = new();
+ ///
+ /// Enemies defeated during this battle (for loot generation
+ /// and recruitment rolls).
+ ///
+ public List DefeatedEnemies { get; private set; } = new();
+
+ ///
+ /// Guards against double-triggering end-of-battle paths.
+ /// Set to true by or
+ /// .
+ ///
+ public bool IsDestroyed { get; private set; }
+
+ ///
+ /// When true, the battle core pauses processing (e.g.
+ /// while a UI overlay is open). Set externally by pause widgets.
+ ///
+ public bool IsWait { get; set; }
+
+ ///
+ /// The whose turn is currently active
+ /// in battle. Set by the battle core when a character is selected
+ /// and cleared when the turn ends or selection is cancelled.
+ /// null means no character is active (out of battle,
+ /// between turns, or idle state). External panels such as the
+ /// inventory panel read this to check whether the active
+ /// character has enough action points before performing
+ /// item-related actions.
+ ///
+ public Character ActiveCharacter { get; set; }
+
+ /// Fired when a character's health reaches 0.
+ public event System.Action OnCharacterDied;
+ ///
+ /// Fired after battle state is cleared (both victory and defeat).
+ /// External systems can hook this to release battle resources.
+ ///
+ public event System.Action OnBattleCleared;
+
+ ///
+ /// Invokes to notify listeners
+ /// (quests, achievements, the battle core) that a character
+ /// has been defeated.
+ ///
+ public void NotifyCharacterDied(Character character)
+ {
+ OnCharacterDied?.Invoke(character);
+ ModEventBus.Publish(new CharacterDiedEvent
+ {
+ CharacterId = character?.ID,
+ CharacterName = character?.Name,
+ Source = "battle",
+ });
+ }
+
+ /// Claims the scene singleton and registers as the provider.
+ protected override void OnEnterTree()
+ {
+ if (Instance != null && GodotObject.IsInstanceValid(Instance))
+ {
+ var scene = GetTree()?.CurrentScene?.Name ?? "?";
+ var existingPath = Instance.GetPath().ToString();
+ var myPath = GetPath().ToString();
+ GameLogger.Log("DUPLICATE", $"BattleSceneContext.OnEnterTree: Instance ALREADY SET by '{existingPath}', overwriting with duplicate at '{myPath}' on scene '{scene}'", LogLevel.Error);
+ }
+ Instance = this;
+ ActiveCharacterRegistry.Provider = this;
+ }
+
+ /// Releases the singleton slot and the active-character provider so a later scene can claim them.
+ protected override void OnExitTree()
+ {
+ base.OnExitTree();
+ if (Instance == this)
+ Instance = null;
+ if (ReferenceEquals(ActiveCharacterRegistry.Provider, this))
+ ActiveCharacterRegistry.Provider = null;
+ }
+
+ private List pendingPlayerCards;
+ private List pendingEnemyCards;
+ private IBattleCore activeBattleCore;
+
+ ///
+ /// The battle core driving this scene, resolved from
+ /// when the scene becomes ready. Exposed
+ /// so tooling and tests can query the active core (for example for
+ /// ) without reaching into UI nodes.
+ ///
+ public IBattleCore ActiveBattleCore => activeBattleCore;
+
+ /// Caches the battle backdrop node, resolves the active and starts the battle.
+ protected override void OnReady()
+ {
+ battleBg = GetNodeOrNull("BattleBg");
+
+ if (battleBg == null)
+ {
+ GameLogger.Log("BATTLE", "battleBg not found!", LogLevel.Error);
+ return;
+ }
+
+ BattleActionMachine = GetNodeOrNull("ToolPanel/BattleActionMachine");
+ _battleSpeedControl = GetNodeOrNull("BattleSpeedControl") as IBattleSpeedControl;
+ deadground = GetNodeOrNull("Deadground");
+
+ winground = GetNodeOrNull("Winground");
+
+ var data = BattleRequest.Current;
+ if (data == null)
+ {
+ GameLogger.Log("BATTLE", "BattleRequest.Current is null!", LogLevel.Error);
+ return;
+ }
+
+ GameLogger.Log("BATTLE", $"set battle background: battleBg={(battleBg != null ? "found" : "NULL")}, texture={(data.Background != null ? "has" : "NULL")}", battleBg == null || data.Background == null ? LogLevel.Error : LogLevel.Debug);
+
+ if (data.Background != null)
+ {
+ battleBg.Texture = data.Background;
+ GameLogger.Log("BATTLE", $" battleBg.Texture assigned: width={battleBg.Texture.GetWidth()}, height={battleBg.Texture.GetHeight()}", LogLevel.Debug);
+ }
+ else
+ {
+ GameLogger.Log("BATTLE", $" data.Background is NULL — no background texture set", LogLevel.Error);
+ }
+
+ var timedShader = Shaders.Resolve("timed_sprite");
+ if (timedShader != null)
+ {
+ var bgMaterial = new ShaderMaterial { Shader = timedShader };
+ battleBg.Material = bgMaterial;
+
+ if (data.DepthMap != null)
+ bgMaterial.SetShaderParameter("depth_mask", data.DepthMap);
+ if (data.AlbedoMap != null)
+ bgMaterial.SetShaderParameter("albedo_map", data.AlbedoMap);
+
+ var lights = GetNodeOrNull("Lights");
+ if (lights != null)
+ {
+ var viewportSize = GetViewport().GetVisibleRect().Size;
+ lights.SetPlayerLight(viewportSize / 2, 500f, 1f);
+ lights.RegisterMaterial(bgMaterial);
+ }
+ GameLogger.Log("BATTLE", $"battleBg shader: timed_sprite applied, depthMap={(data.DepthMap != null ? "has" : "NULL")}, albedoMap={(data.AlbedoMap != null ? "has" : "NULL")}, lightsCtrl={(lights != null ? "found" : "NULL")}", lights == null ? LogLevel.Error : LogLevel.Debug);
+ }
+ else
+ {
+ GameLogger.Log("BATTLE", "battleBg shader: timed_sprite NOT FOUND", LogLevel.Error);
+ }
+
+ pendingPlayerCards = new List();
+ foreach (var characterType in GameData.Instance.Runtime.CharacterData.BattleSet)
+ {
+ var characterInfo = GameData.Instance.Runtime.CharacterData.Characters[characterType];
+ var template = CharacterFactory.Instance.Get(characterInfo.ID);
+ var character = template != null ? template.Copy() : new Character { ID = characterInfo.ID };
+ character.Attributes = characterInfo.Attributes ?? new CharacterAttributes();
+ character.Level = characterInfo.Level;
+ character.Exp = characterInfo.Exp;
+ if (characterInfo.ActionSlots != null && characterInfo.ActionSlots.Any(id => !string.IsNullOrEmpty(id)))
+ {
+ character.Actions = characterInfo.ActionSlots
+ .Where(id => !string.IsNullOrEmpty(id))
+ .Select(id => ActionCharacterFactory.Instance.Get(id))
+ .Where(a => a != null)
+ .ToList();
+ }
+ pendingPlayerCards.Add(character);
+ }
+
+ pendingEnemyCards = data.EnemiesCards?.Where(c => c != null).ToList() ?? new List();
+
+ if (!string.IsNullOrEmpty(data.ActiveBattleCore) && BattleCoreRegistry.Instance.HasCore(data.ActiveBattleCore))
+ BattleCoreRegistry.Instance.SetActive(data.ActiveBattleCore);
+
+ try
+ {
+ activeBattleCore = BattleCoreRegistry.Instance.GetActive();
+ }
+ catch
+ {
+ activeBattleCore = null;
+ }
+
+ if (activeBattleCore == null)
+ {
+ GameLogger.Log("BATTLE", "No battle core available", LogLevel.Error);
+ return;
+ }
+
+ Callable.From(DeferredInit).CallDeferred();
+ }
+
+ private void DeferredInit()
+ {
+ if (IsDestroyed || activeBattleCore == null)
+ return;
+
+ var ctx = new BattleContextImpl(this);
+ var playerArray = pendingPlayerCards?.ToArray() ?? new Character[0];
+ var enemyArray = pendingEnemyCards?.ToArray() ?? new Character[0];
+
+ activeBattleCore.Init(playerArray, enemyArray, ctx);
+ activeBattleCore.Start();
+
+ ModEventBus.Publish(new BattleStartedEvent
+ {
+ BattleType = activeBattleCore.GetType().Name,
+ Enemies = enemyArray.Select(c => c.Name).ToArray(),
+ });
+ }
+
+ ///
+ /// Triggers the defeat screen: sets ,
+ /// resets battle speed, clears battle state, and shows the
+ /// deadground widget.
+ ///
+ public void ShowDeadground()
+ {
+ if (IsDestroyed)
+ return;
+ IsDestroyed = true;
+ _battleSpeedControl?.ResetToNormal();
+ ClearBattle("defeat");
+ deadground?.Show();
+ }
+
+ ///
+ /// Triggers the victory screen: sets ,
+ /// resets battle speed, clears battle state, and shows the
+ /// winground widget.
+ ///
+ public void ShowWinground()
+ {
+ if (IsDestroyed)
+ return;
+ IsDestroyed = true;
+ _battleSpeedControl?.ResetToNormal();
+ ClearBattle("victory");
+ winground?.Show();
+ }
+
+ ///
+ /// Records a defeated enemy: adds it to
+ /// and distributes its EXP
+ /// (Level * Exp) equally among all surviving player
+ /// characters in .
+ ///
+ public void RecordEnemyDefeated(Character enemy)
+ {
+ if (enemy == null)
+ return;
+ DefeatedEnemies.Add(enemy);
+
+ var enemyExp = enemy.Level * enemy.Exp;
+ var alivePlayers = 0;
+ foreach (var c in pendingPlayerCards)
+ {
+ if (c?.Attributes?.Health?.Value > 0)
+ alivePlayers++;
+ }
+ if (alivePlayers == 0)
+ return;
+
+ var expPerPlayer = enemyExp / alivePlayers;
+ foreach (var c in pendingPlayerCards)
+ {
+ if (c?.Attributes?.Health?.Value <= 0)
+ continue;
+ var id = c.ID;
+ if (id == null)
+ continue;
+ PlayerExpGained.TryGetValue(id, out var current);
+ PlayerExpGained[id] = current + expPerPlayer;
+ }
+ }
+
+ ///
+ /// Stops the battle and publishes . The
+ /// outcome is passed in by the caller: both screens set
+ /// before clearing, so it cannot be derived
+ /// from that flag (it used to be, and every battle reported "defeat").
+ ///
+ private void ClearBattle(string result)
+ {
+ BattleActionMachine?.StopMachine();
+ ActiveCharacter = null;
+ pendingPlayerCards?.Clear();
+ pendingEnemyCards?.Clear();
+ OnBattleCleared?.Invoke();
+ ModEventBus.Publish(new BattleEndedEvent
+ {
+ Result = result,
+ });
+ }
+ }
+}
diff --git a/src/BattleSetCoreAction.cs b/src/BattleSetCoreAction.cs
index d3392a7..ec35f4a 100644
--- a/src/BattleSetCoreAction.cs
+++ b/src/BattleSetCoreAction.cs
@@ -1,40 +1,40 @@
-using Cthangover.Core.Actions;
-
-namespace Cthangover.Core.Battle
-{
- ///
- /// Selects which battle core (combat ruleset) the next battle will use.
- /// The "core" variable maps to a entry by
- /// string ID. Must be called before a battle starts, as
- /// reads the active core at initialization time.
- ///
- public class BattleSetCoreAction : IScenarioAction
- {
- ///
- /// Registered as "battle.set_core" — selects the combat ruleset
- /// for the next battle encounter. If "core" is empty or missing, logs a
- /// warning and returns without modifying the registry.
- ///
- public string Name => "battle.set_core";
-
- ///
- /// Reads the "core" variable and sets it as the active battle core
- /// via . The core determines
- /// combat mechanics (turn order, ability system, etc.) for the
- /// next initiated battle.
- ///
- public void Run(IActionContext ctx)
- {
- var core = ctx.GetParam("core");
- if (string.IsNullOrEmpty(core))
- {
- ctx.Log("EVENT", "BattleSetCoreAction: missing 'core' variable");
- return;
- }
-
- BattleCoreRegistry.Instance.SetActive(core);
- ctx.Log("EVENT", $"BattleSetCoreAction: battle core set to '{core}'");
- }
- }
-}
+using Cthangover.Core.Actions;
+
+namespace Cthangover.Core.Battle
+{
+ ///
+ /// Selects which battle core (combat ruleset) the next battle will use.
+ /// The "core" variable maps to a entry by
+ /// string ID. Must be called before a battle starts, as
+ /// reads the active core at initialization time.
+ ///
+ public class BattleSetCoreAction : IScenarioAction
+ {
+ ///
+ /// Registered as "battle.set_core" — selects the combat ruleset
+ /// for the next battle encounter. If "core" is empty or missing, logs a
+ /// warning and returns without modifying the registry.
+ ///
+ public string Name => "battle.set_core";
+
+ ///
+ /// Reads the "core" variable and sets it as the active battle core
+ /// via . The core determines
+ /// combat mechanics (turn order, ability system, etc.) for the
+ /// next initiated battle.
+ ///
+ public void Run(IActionContext ctx)
+ {
+ var core = ctx.GetParam("core");
+ if (string.IsNullOrEmpty(core))
+ {
+ ctx.Log("EVENT", "BattleSetCoreAction: missing 'core' variable");
+ return;
+ }
+
+ BattleCoreRegistry.Instance.SetActive(core);
+ ctx.Log("EVENT", $"BattleSetCoreAction: battle core set to '{core}'");
+ }
+ }
+}
diff --git a/src/BattleSpeedControl.cs b/src/BattleSpeedControl.cs
index 73327e9..7cbd738 100644
--- a/src/BattleSpeedControl.cs
+++ b/src/BattleSpeedControl.cs
@@ -1,110 +1,110 @@
-using System.Collections.Generic;
-using Cthangover.Core.Battle;
-using Cthangover.Core.Settings;
-using Cthangover.Core.UI.Theming;
-using Godot;
-
-namespace Cthangover.TurnBattle
-{
- ///
- /// In-battle speed toggle bar (x1/x4/x8). Directly sets
- /// Godot.Engine.TimeScale to globally accelerate the entire game loop —
- /// not just animations. Persists the choice to GameData.Settings
- /// so it survives scene reloads. restores 1x
- /// without saving and is the part the core scene consumes through
- /// when battle ends.
- ///
- public partial class BattleSpeedControl : Control, IBattleSpeedControl
- {
- private readonly Dictionary _buttons = new();
- private int _activeSpeed;
-
- ///
- /// Builds the speed bar in the constructor. Godot never runs a
- /// _Ready override declared in a mod assembly, so the buttons and the
- /// saved-speed restore must happen here. Anchors are used instead of a
- /// viewport-size lookup because the viewport is unavailable before the node
- /// enters the tree.
- ///
- public BattleSpeedControl()
- {
- CthangoverTheme.Apply(this);
-
- var speeds = new[] { 1, 4, 8 };
- var hbox = new HBoxContainer { Name = "SpeedButtons" };
- hbox.Alignment = BoxContainer.AlignmentMode.Begin;
-
- foreach (var speed in speeds)
- {
- var btn = new Button
- {
- Text = $"x{speed}",
- CustomMinimumSize = new Vector2(48, 32)
- };
- btn.Pressed += () => OnSpeedSelected(speed);
- hbox.AddChild(btn);
- _buttons[speed] = btn;
- }
-
- AddChild(hbox);
-
- AnchorLeft = 1f;
- AnchorRight = 1f;
- AnchorTop = 0f;
- AnchorBottom = 0f;
- OffsetLeft = -200f;
- OffsetRight = -20f;
- OffsetTop = 20f;
- OffsetBottom = 52f;
-
- ApplySavedSpeed();
- }
-
- ///
- /// Restores the speed from .
- /// Called on _Ready to reapply the saved preference.
- ///
- public void ApplySavedSpeed()
- {
- var speed = GameData.Instance.Settings.BattleSpeed;
- if (speed < 1 || speed > 8)
- speed = 1;
-
- _activeSpeed = speed;
- Godot.Engine.TimeScale = speed;
- UpdateHighlight();
- }
-
- ///
- /// Resets to 1.0x without
- /// persisting to settings. Used when battle ends so the lobby
- /// and menus run at normal speed regardless of the player's
- /// in-battle preference.
- ///
- public void ResetToNormal()
- {
- Godot.Engine.TimeScale = 1.0f;
- }
-
- private void OnSpeedSelected(int speed)
- {
- _activeSpeed = speed;
- Godot.Engine.TimeScale = speed;
-
- var settings = GameData.Instance.Settings;
- settings.BattleSpeed = speed;
- settings.Save();
-
- UpdateHighlight();
- }
-
- private void UpdateHighlight()
- {
- foreach (var kv in _buttons)
- {
- var isActive = kv.Key == _activeSpeed;
- kv.Value.Modulate = isActive ? UiTokens.Success : UiTokens.White;
- }
- }
- }
-}
+using System.Collections.Generic;
+using Cthangover.Core.Battle;
+using Cthangover.Core.Settings;
+using Cthangover.Core.UI.Theming;
+using Godot;
+
+namespace Cthangover.TurnBattle
+{
+ ///
+ /// In-battle speed toggle bar (x1/x4/x8). Directly sets
+ /// Godot.Engine.TimeScale to globally accelerate the entire game loop —
+ /// not just animations. Persists the choice to GameData.Settings
+ /// so it survives scene reloads. restores 1x
+ /// without saving and is the part the core scene consumes through
+ /// when battle ends.
+ ///
+ public partial class BattleSpeedControl : Control, IBattleSpeedControl
+ {
+ private readonly Dictionary _buttons = new();
+ private int _activeSpeed;
+
+ ///
+ /// Builds the speed bar in the constructor. Godot never runs a
+ /// _Ready override declared in a mod assembly, so the buttons and the
+ /// saved-speed restore must happen here. Anchors are used instead of a
+ /// viewport-size lookup because the viewport is unavailable before the node
+ /// enters the tree.
+ ///
+ public BattleSpeedControl()
+ {
+ CthangoverTheme.Apply(this);
+
+ var speeds = new[] { 1, 4, 8 };
+ var hbox = new HBoxContainer { Name = "SpeedButtons" };
+ hbox.Alignment = BoxContainer.AlignmentMode.Begin;
+
+ foreach (var speed in speeds)
+ {
+ var btn = new Button
+ {
+ Text = $"x{speed}",
+ CustomMinimumSize = new Vector2(48, 32)
+ };
+ btn.Pressed += () => OnSpeedSelected(speed);
+ hbox.AddChild(btn);
+ _buttons[speed] = btn;
+ }
+
+ AddChild(hbox);
+
+ AnchorLeft = 1f;
+ AnchorRight = 1f;
+ AnchorTop = 0f;
+ AnchorBottom = 0f;
+ OffsetLeft = -200f;
+ OffsetRight = -20f;
+ OffsetTop = 20f;
+ OffsetBottom = 52f;
+
+ ApplySavedSpeed();
+ }
+
+ ///
+ /// Restores the speed from .
+ /// Called on _Ready to reapply the saved preference.
+ ///
+ public void ApplySavedSpeed()
+ {
+ var speed = GameData.Instance.Settings.BattleSpeed;
+ if (speed < 1 || speed > 8)
+ speed = 1;
+
+ _activeSpeed = speed;
+ Godot.Engine.TimeScale = speed;
+ UpdateHighlight();
+ }
+
+ ///
+ /// Resets to 1.0x without
+ /// persisting to settings. Used when battle ends so the lobby
+ /// and menus run at normal speed regardless of the player's
+ /// in-battle preference.
+ ///
+ public void ResetToNormal()
+ {
+ Godot.Engine.TimeScale = 1.0f;
+ }
+
+ private void OnSpeedSelected(int speed)
+ {
+ _activeSpeed = speed;
+ Godot.Engine.TimeScale = speed;
+
+ var settings = GameData.Instance.Settings;
+ settings.BattleSpeed = speed;
+ settings.Save();
+
+ UpdateHighlight();
+ }
+
+ private void UpdateHighlight()
+ {
+ foreach (var kv in _buttons)
+ {
+ var isActive = kv.Key == _activeSpeed;
+ kv.Value.Modulate = isActive ? UiTokens.Success : UiTokens.White;
+ }
+ }
+ }
+}
diff --git a/src/ChangedAttributes.cs b/src/ChangedAttributes.cs
index ad751d1..57175f4 100644
--- a/src/ChangedAttributes.cs
+++ b/src/ChangedAttributes.cs
@@ -1,30 +1,30 @@
-namespace Cthangover.Core.Characters
-{
- ///
- /// Result struct for battle action resolution. Source and Target carry
- /// Damage/Defence snapshots for both sides of the exchange. Result indicates
- /// whether the action succeeded (e.g. hit landed vs. missed/blocked).
- /// A struct rather than a class because these are short-lived stack values
- /// created in tight battle loops — no heap allocation.
- ///
- public struct ChangedAttributes
- {
- ///
- /// Snapshot of the action source's damage and defence after the
- /// exchange.
- ///
- public CharacterSet Source;
- ///
- /// Snapshot of the action target's damage and defence after the
- /// exchange.
- ///
- public CharacterSet Target;
- ///
- /// Whether the action succeeded (e.g. hit landed vs. missed or
- /// blocked).
- ///
- public bool Result;
-
- }
-
-}
+namespace Cthangover.Core.Characters
+{
+ ///
+ /// Result struct for battle action resolution. Source and Target carry
+ /// Damage/Defence snapshots for both sides of the exchange. Result indicates
+ /// whether the action succeeded (e.g. hit landed vs. missed/blocked).
+ /// A struct rather than a class because these are short-lived stack values
+ /// created in tight battle loops — no heap allocation.
+ ///
+ public struct ChangedAttributes
+ {
+ ///
+ /// Snapshot of the action source's damage and defence after the
+ /// exchange.
+ ///
+ public CharacterSet Source;
+ ///
+ /// Snapshot of the action target's damage and defence after the
+ /// exchange.
+ ///
+ public CharacterSet Target;
+ ///
+ /// Whether the action succeeded (e.g. hit landed vs. missed or
+ /// blocked).
+ ///
+ public bool Result;
+
+ }
+
+}
diff --git a/src/CharacterSet.cs b/src/CharacterSet.cs
index 9802ab6..d3fa44c 100644
--- a/src/CharacterSet.cs
+++ b/src/CharacterSet.cs
@@ -1,20 +1,20 @@
-namespace Cthangover.Core.Characters
-{
- ///
- /// Lightweight snapshot of damage/defence values for battle action results.
- /// A struct to avoid heap allocation in the battle loop where these are
- /// created for every action exchange. Used by ChangedAttributes to carry
- /// both source and target state.
- ///
- public struct CharacterSet
- {
- ///
- /// Damage dealt or received in this exchange.
- ///
- public int Damage;
- ///
- /// Defence value at the time of the exchange.
- ///
- public int Defence;
- }
-}
+namespace Cthangover.Core.Characters
+{
+ ///
+ /// Lightweight snapshot of damage/defence values for battle action results.
+ /// A struct to avoid heap allocation in the battle loop where these are
+ /// created for every action exchange. Used by ChangedAttributes to carry
+ /// both source and target state.
+ ///
+ public struct CharacterSet
+ {
+ ///
+ /// Damage dealt or received in this exchange.
+ ///
+ public int Damage;
+ ///
+ /// Defence value at the time of the exchange.
+ ///
+ public int Defence;
+ }
+}
diff --git a/src/DeadgroundBehaviour.cs b/src/DeadgroundBehaviour.cs
index 419b359..5605b94 100644
--- a/src/DeadgroundBehaviour.cs
+++ b/src/DeadgroundBehaviour.cs
@@ -1,63 +1,63 @@
-using Cthangover.Core.Audio;
-using Cthangover.Core.Battle;
-using Cthangover.Core.Mods;
-using Cthangover.Core.Mods.Resolvers;
-using Cthangover.Core.Scenes;
-using Cthangover.Core.Settings;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Theming;
-using Cthangover.Core.Utils;
-using Godot;
-
-namespace Cthangover.TurnBattle
-{
- ///
- /// Defeat screen widget. Minimal compared to WingroundBehaviour —
- /// only sets the deadground background texture (resolved from mods)
- /// and offers a Main Menu button that clears BattleData and reloads
- /// the menu scene. No loot, no EXP.
- ///
- public partial class DeadgroundBehaviour : TransitionWidget
- {
- ///
- /// Clears battle data and switches to the main menu scene.
- /// Bound to the "Main Menu" button on the defeat screen.
- ///
- public void ToMainMenuClick()
- {
- var sceneService = GetNode("/root/GodotSceneService");
- sceneService?.SwitchToMenu();
- BattleRequest.Current = null;
- }
-
- ///
- /// Plays the defeat sound, resolves the deadground background
- /// texture from mods, and shows the transition widget.
- ///
- public override void Show()
- {
- CthangoverTheme.Apply(this);
- CthangoverTheme.ApplyPrimary(GetNodeOrNull