From 13da8f87a253706a7598e6123d9224ecd87234a6 Mon Sep 17 00:00:00 2001 From: iisaev Date: Fri, 16 Oct 2026 16:18:24 +0000 Subject: [PATCH] upd: sync local changes Co-Authored-By: Claude Opus 5.5 --- locale/en.properties | 2 ++ locale/ru.properties | 2 ++ manifest.json | 4 ++-- scenes/battle/battle.tscn | 2 +- src/Actions/BattleActionMachine.cs | 3 +++ src/BattleEndedEvent.cs | 9 +++++++++ src/BattleSceneContext.cs | 7 ++++++- src/BattleService.cs | 2 +- src/BattleSpeedControl.cs | 10 +++++----- src/BattleStartedEvent.cs | 9 +++++++++ src/CharacterDiedEvent.cs | 3 +++ src/DeadgroundBehaviour.cs | 4 ++-- src/GDWingroundListener.cs | 4 ++++ src/IBattleSpeedControl.cs | 2 +- src/ShowDamageBehaviour.cs | 2 +- src/StatusEffectActionFactory.cs | 7 ++++++- src/StatusEffectInfoFactory.cs | 1 + src/WingroundBehaviour.cs | 6 +++--- 18 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 locale/en.properties create mode 100644 locale/ru.properties diff --git a/locale/en.properties b/locale/en.properties new file mode 100644 index 0000000..6b0f784 --- /dev/null +++ b/locale/en.properties @@ -0,0 +1,2 @@ +mod_turn_battle_base_name = Turn-based Battle Engine +mod_turn_battle_base_desc = Turn-based battle platform: battle ruleset contracts (IBattleCore), the core registry, action executors, status effects, the battle scene, victory/defeat screens and EXP/loot reports. diff --git a/locale/ru.properties b/locale/ru.properties new file mode 100644 index 0000000..6daf4a7 --- /dev/null +++ b/locale/ru.properties @@ -0,0 +1,2 @@ +mod_turn_battle_base_name = Движок пошаговых боёв +mod_turn_battle_base_desc = Платформа пошаговых боёв: контракты боевых ядер (IBattleCore), реестр ядер, исполнители действий, статус-эффекты, боевая сцена, экраны победы/поражения и отчёты опыта и добычи. diff --git a/manifest.json b/manifest.json index 24b1d49..55884df 100644 --- a/manifest.json +++ b/manifest.json @@ -1,4 +1,4 @@ -{ +{ "id": "turn_battle_base", "name": "Turn-based battle engine", "version": "2.0.0", @@ -8,5 +8,5 @@ "sources": [ "src/**/*.cs" ], - "depends": [ "visual_novel_core>=1", "interface>=1" ] + "depends": [ "characters_base>=1", "visual_novel_core>=1", "interface>=1", "items_base>=1", "quests_base>=1", "time_base>=1" ] } diff --git a/scenes/battle/battle.tscn b/scenes/battle/battle.tscn index eb76acd..80ceb9e 100644 --- a/scenes/battle/battle.tscn +++ b/scenes/battle/battle.tscn @@ -4,7 +4,7 @@ [ext_resource type="Script" path="src/BattleSceneContext.cs" id="BattleSceneContextScript"] [ext_resource type="Script" path="src/BattleSpeedControl.cs" id="BattleSpeedControlScript"] [ext_resource type="PackedScene" uid="uid://bjhtml8ujba7p" path="res://scenes/battle/deadground.tscn" id="DeadgroundScene"] -[ext_resource type="Script" uid="uid://icsu2hv2c68r" path="res://Core/UI/Lights/UiLightController.cs" id="UiLightControllerScript"] +[ext_resource type="Script" uid="uid://icsu2hv2c68r" path="res://mods/time_base/src/Lights/UiLightController.cs" id="UiLightControllerScript"] [ext_resource type="PackedScene" uid="uid://b3yq032kqhtn4" path="res://scenes/battle/winground.tscn" id="WingroundScene"] [node name="BattleScene" type="Control" unique_id=760066045] diff --git a/src/Actions/BattleActionMachine.cs b/src/Actions/BattleActionMachine.cs index 1e1fb9c..d20e2ab 100644 --- a/src/Actions/BattleActionMachine.cs +++ b/src/Actions/BattleActionMachine.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Cthangover.Core.UI; using Godot; +using Cthangover.Mods.Interface.Base; namespace Cthangover.Core.Battle.Actions { @@ -37,12 +38,14 @@ namespace Cthangover.Core.Battle.Actions /// 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) diff --git a/src/BattleEndedEvent.cs b/src/BattleEndedEvent.cs index 50c7159..733817f 100644 --- a/src/BattleEndedEvent.cs +++ b/src/BattleEndedEvent.cs @@ -1,11 +1,20 @@ namespace Cthangover.Core.Events { + /// + /// Published when the battle concludes (victory or defeat), after the + /// victory/defeat screen resolves. Mods can use it for post-battle cleanup + /// or story state. Note: the group defeat with loot and recruitment payload + /// is the separate , which fires from + /// the victory screen. + /// [LifecycleEvent] [EventKey("battle_ended")] public class BattleEndedEvent : ModEvent { + /// Outcome reported by the battle core (e.g. "victory", "defeat"). public string Result { get; init; } + /// Number of turns the battle lasted. public int Turns { get; init; } } } diff --git a/src/BattleSceneContext.cs b/src/BattleSceneContext.cs index d11a1fb..82d88a8 100644 --- a/src/BattleSceneContext.cs +++ b/src/BattleSceneContext.cs @@ -5,12 +5,14 @@ using Cthangover.Core.Factories.Impls; using Cthangover.Core.Mods; using Cthangover.Core.Settings; using Cthangover.Core.UI; -using Cthangover.Core.UI.Lights; +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 { /// @@ -120,6 +122,7 @@ namespace Cthangover.Core.Battle }); } + /// Claims the scene singleton and registers as the provider. protected override void OnEnterTree() { if (Instance != null && GodotObject.IsInstanceValid(Instance)) @@ -133,6 +136,7 @@ namespace Cthangover.Core.Battle 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(); @@ -154,6 +158,7 @@ namespace Cthangover.Core.Battle /// public IBattleCore ActiveBattleCore => activeBattleCore; + /// Caches the battle backdrop node, resolves the active and starts the battle. protected override void OnReady() { battleBg = GetNodeOrNull("BattleBg"); diff --git a/src/BattleService.cs b/src/BattleService.cs index bdc2c41..ec66317 100644 --- a/src/BattleService.cs +++ b/src/BattleService.cs @@ -4,7 +4,7 @@ using Cthangover.Core.Factories.Impls; using Cthangover.Core.Quests; using Cthangover.Core.Scenes; using Cthangover.Core.Settings; -using Cthangover.Core.UI.Lights; +using Cthangover.Mods.Time; using Cthangover.Core.Utils; namespace Cthangover.Core.Battle diff --git a/src/BattleSpeedControl.cs b/src/BattleSpeedControl.cs index 64bbfd9..7cbd738 100644 --- a/src/BattleSpeedControl.cs +++ b/src/BattleSpeedControl.cs @@ -8,7 +8,7 @@ namespace Cthangover.TurnBattle { /// /// In-battle speed toggle bar (x1/x4/x8). Directly sets - /// Engine.TimeScale to globally accelerate the entire game loop — + /// 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 @@ -71,25 +71,25 @@ namespace Cthangover.TurnBattle speed = 1; _activeSpeed = speed; - Engine.TimeScale = speed; + Godot.Engine.TimeScale = speed; UpdateHighlight(); } /// - /// Resets to 1.0x without + /// 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() { - Engine.TimeScale = 1.0f; + Godot.Engine.TimeScale = 1.0f; } private void OnSpeedSelected(int speed) { _activeSpeed = speed; - Engine.TimeScale = speed; + Godot.Engine.TimeScale = speed; var settings = GameData.Instance.Settings; settings.BattleSpeed = speed; diff --git a/src/BattleStartedEvent.cs b/src/BattleStartedEvent.cs index c86c4d8..f6ff610 100644 --- a/src/BattleStartedEvent.cs +++ b/src/BattleStartedEvent.cs @@ -1,10 +1,19 @@ namespace Cthangover.Core.Events { + /// + /// Published when a battle encounter is initialised (from + /// , before the battle + /// scene loads). Lets mods observe or prepare for the fight — e.g. pre-buff + /// party members or record the encounter for analytics. The + /// [LifecycleEvent] marker keeps it out of condition-cached flows. + /// [LifecycleEvent] [EventKey("battle_started")] public class BattleStartedEvent : ModEvent { + /// Logical name of the battle scene route being entered (e.g. "Battle"). public string BattleType { get; init; } + /// Template ids of the enemies participating in the encounter. public string[] Enemies { get; init; } } } diff --git a/src/CharacterDiedEvent.cs b/src/CharacterDiedEvent.cs index 32642c0..99319f0 100644 --- a/src/CharacterDiedEvent.cs +++ b/src/CharacterDiedEvent.cs @@ -8,8 +8,11 @@ namespace Cthangover.Core.Events [EventKey("character_died")] public class CharacterDiedEvent : ModEvent { + /// Template id of the character that died. public string CharacterId { get; init; } + /// Display name at time of death (resolved locale string). public string CharacterName { get; init; } + /// Free-form id of the reporting battle system. public string Source { get; init; } } } diff --git a/src/DeadgroundBehaviour.cs b/src/DeadgroundBehaviour.cs index 8d977e1..5605b94 100644 --- a/src/DeadgroundBehaviour.cs +++ b/src/DeadgroundBehaviour.cs @@ -4,7 +4,7 @@ using Cthangover.Core.Mods; using Cthangover.Core.Mods.Resolvers; using Cthangover.Core.Scenes; using Cthangover.Core.Settings; -using Cthangover.Core.UI.Base.Lists; +using Cthangover.Mods.Interface.Base.Lists; using Cthangover.Core.UI.Theming; using Cthangover.Core.Utils; using Godot; @@ -41,7 +41,7 @@ namespace Cthangover.TurnBattle GameLogger.Log("BATTLE", $"Deadground.Show() ENTER: Visible={Visible}, GodotVisible={base.Visible}", LogLevel.Debug); - var audioService = GetNode("/root/AudioService"); + var audioService = GetNodeOrNull("/root/AudioService") as IAudioService; audioService?.PlaySound("battle/deadground", SoundType.UI); var bg = GetNodeOrNull("TransitionBg"); diff --git a/src/GDWingroundListener.cs b/src/GDWingroundListener.cs index 0762e96..b168cdb 100644 --- a/src/GDWingroundListener.cs +++ b/src/GDWingroundListener.cs @@ -19,6 +19,7 @@ namespace Cthangover.Core.Battle { private readonly GodotObject _instance; + /// Registry key reported by the script's get_winground_listener_id(); deduplicates on rescan. public string Id { get; } /// @@ -31,6 +32,7 @@ namespace Cthangover.Core.Battle _instance = instance; } + /// Calls the script's on_winground_before_exp(ctx) if implemented; no-op otherwise. public void OnBeforeExpAward(WingroundContext ctx) { if (!_instance.HasMethod("on_winground_before_exp")) @@ -41,6 +43,7 @@ namespace Cthangover.Core.Battle BattleGDScriptBridge.SyncWingroundBack(gdCtx, ctx); } + /// Calls the script's on_winground_after_loot(ctx) if implemented; no-op otherwise. public void OnAfterLootGenerated(WingroundContext ctx) { if (!_instance.HasMethod("on_winground_after_loot")) @@ -51,6 +54,7 @@ namespace Cthangover.Core.Battle BattleGDScriptBridge.SyncWingroundBack(gdCtx, ctx); } + /// Calls the script's on_winground_before_show(ctx) if implemented; no-op otherwise. public void OnBeforeShow(WingroundContext ctx) { if (!_instance.HasMethod("on_winground_before_show")) diff --git a/src/IBattleSpeedControl.cs b/src/IBattleSpeedControl.cs index 9b7a916..9fc3851 100644 --- a/src/IBattleSpeedControl.cs +++ b/src/IBattleSpeedControl.cs @@ -9,7 +9,7 @@ namespace Cthangover.Core.Battle public interface IBattleSpeedControl { /// - /// Restores Engine.TimeScale to 1x without saving the preference. + /// Restores Godot.Engine.TimeScale to 1x without saving the preference. /// Called when the battle ends so menus and the lobby run at normal speed /// regardless of the player's in-battle speed choice. /// diff --git a/src/ShowDamageBehaviour.cs b/src/ShowDamageBehaviour.cs index ad65ce5..d739706 100644 --- a/src/ShowDamageBehaviour.cs +++ b/src/ShowDamageBehaviour.cs @@ -34,7 +34,7 @@ namespace Cthangover.TurnBattle MouseFilter = MouseFilterEnum.Ignore; _label = new Label(); - _label.SetAnchorsPreset(LayoutPreset.Center); + _label.SetAnchorsAndOffsetsPreset(LayoutPreset.Center); _label.HorizontalAlignment = HorizontalAlignment.Center; _label.VerticalAlignment = VerticalAlignment.Center; _label.AddThemeFontSizeOverride("font_size", 28); diff --git a/src/StatusEffectActionFactory.cs b/src/StatusEffectActionFactory.cs index 39ab5c4..6fb665b 100644 --- a/src/StatusEffectActionFactory.cs +++ b/src/StatusEffectActionFactory.cs @@ -21,6 +21,7 @@ namespace Cthangover.Core.Factories.Impls #region Singleton private static readonly Lazy instance = new(() => new StatusEffectActionFactory()); + /// Lazy singleton; construction performs the AppDomain scan. public static StatusEffectActionFactory Instance => instance.Value; public StatusEffectActionFactory() @@ -28,12 +29,16 @@ namespace Cthangover.Core.Factories.Impls actions = new Dictionary(); foreach (var action in Reflections.FindAndCreate()) { - actions.Add(action.ID, action); + // First-wins: the AppDomain scan covers every loaded assembly and mod + // assemblies are never unloaded, so a hot-reload can discover the + // same implementation twice — keep the first instance (ItemActionFactory precedent). + if (!string.IsNullOrEmpty(action.ID) && !actions.ContainsKey(action.ID)) actions[action.ID] = action; } } #endregion + /// Resolves the behaviour set registered under the effect's Actions id; unknown ids are logged and return null. public IStatusActions Get(string id) { if (actions.TryGetValue(id, out var result)) diff --git a/src/StatusEffectInfoFactory.cs b/src/StatusEffectInfoFactory.cs index cbc59ad..a7ee615 100644 --- a/src/StatusEffectInfoFactory.cs +++ b/src/StatusEffectInfoFactory.cs @@ -15,6 +15,7 @@ namespace Cthangover.Core.Factories.Impls public class StatusEffectInfoFactory : FileFactory { private static readonly Lazy lazy = new(() => new StatusEffectInfoFactory()); + /// Lazy singleton facade over the AppServices-owned factory. public static StatusEffectInfoFactory Instance => lazy.Value; } } diff --git a/src/WingroundBehaviour.cs b/src/WingroundBehaviour.cs index 0b6594b..f07ef96 100644 --- a/src/WingroundBehaviour.cs +++ b/src/WingroundBehaviour.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Cthangover.Core.Audio; @@ -9,7 +9,7 @@ using Cthangover.Core.Items; using Cthangover.Core.Mods; using Cthangover.Core.Scenes; using Cthangover.Core.Settings; -using Cthangover.Core.UI.Base.Lists; +using Cthangover.Mods.Interface.Base.Lists; using Cthangover.Core.UI.Theming; using Cthangover.Core.Characters; using Cthangover.Core.Mods.Resolvers; @@ -71,7 +71,7 @@ namespace Cthangover.TurnBattle GameLogger.Log("BATTLE", $"Winground.Show() ENTER: Visible={Visible}, GodotVisible={base.Visible}, txtInfo={(txtInfo != null ? "found" : "NULL")}", LogLevel.Debug); - var audioService = GetNode("/root/AudioService"); + var audioService = GetNodeOrNull("/root/AudioService") as IAudioService; audioService?.PlaySound("battle/winground", SoundType.UI); var bg = GetNodeOrNull("TransitionBg");