upd: sync local changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
46afb4b533
commit
13da8f87a2
18 files changed
+61
-18
No files matched your search
@@ -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.
|
||||
@@ -0,0 +1,2 @@
|
||||
mod_turn_battle_base_name = Движок пошаговых боёв
|
||||
mod_turn_battle_base_desc = Платформа пошаговых боёв: контракты боевых ядер (IBattleCore), реестр ядер, исполнители действий, статус-эффекты, боевая сцена, экраны победы/поражения и отчёты опыта и добычи.
|
||||
+2
-2
@@ -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" ]
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public event StopMachineDelegate OnStopMachine;
|
||||
|
||||
/// <summary>Stops the machine and fires <see cref="OnStopMachine"/> when the node leaves the tree, so subscribers never wait on a dead node.</summary>
|
||||
protected override void OnExitTree()
|
||||
{
|
||||
base.OnExitTree();
|
||||
StopMachine();
|
||||
}
|
||||
|
||||
/// <summary>Drives the queued actions once per frame; fires <see cref="OnStopMachine"/> when the queue drains.</summary>
|
||||
protected override void OnProcess(double delta)
|
||||
{
|
||||
if (Actions.Count == 0 && CurrentAction == null)
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
namespace Cthangover.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 <see cref="CharactersDefeatedEvent"/>, which fires from
|
||||
/// the victory screen.
|
||||
/// </summary>
|
||||
[LifecycleEvent]
|
||||
[EventKey("battle_ended")]
|
||||
public class BattleEndedEvent : ModEvent
|
||||
{
|
||||
/// <summary>Outcome reported by the battle core (e.g. <c>"victory"</c>, <c>"defeat"</c>).</summary>
|
||||
public string Result { get; init; }
|
||||
|
||||
/// <summary>Number of turns the battle lasted.</summary>
|
||||
public int Turns { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
@@ -120,6 +122,7 @@ namespace Cthangover.Core.Battle
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Claims the scene singleton and registers as the <see cref="ActiveCharacterRegistry"/> provider.</summary>
|
||||
protected override void OnEnterTree()
|
||||
{
|
||||
if (Instance != null && GodotObject.IsInstanceValid(Instance))
|
||||
@@ -133,6 +136,7 @@ namespace Cthangover.Core.Battle
|
||||
ActiveCharacterRegistry.Provider = this;
|
||||
}
|
||||
|
||||
/// <summary>Releases the singleton slot and the active-character provider so a later scene can claim them.</summary>
|
||||
protected override void OnExitTree()
|
||||
{
|
||||
base.OnExitTree();
|
||||
@@ -154,6 +158,7 @@ namespace Cthangover.Core.Battle
|
||||
/// </summary>
|
||||
public IBattleCore ActiveBattleCore => activeBattleCore;
|
||||
|
||||
/// <summary>Caches the battle backdrop node, resolves the active <see cref="IBattleCore"/> and starts the battle.</summary>
|
||||
protected override void OnReady()
|
||||
{
|
||||
battleBg = GetNodeOrNull<TextureRect>("BattleBg");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Cthangover.TurnBattle
|
||||
{
|
||||
/// <summary>
|
||||
/// 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. <see cref="ResetToNormal"/> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets <see cref="Engine.TimeScale"/> to 1.0x without
|
||||
/// Resets <see cref="Godot.Engine.TimeScale"/> 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.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
namespace Cthangover.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Published when a battle encounter is initialised (from
|
||||
/// <see cref="Cthangover.Core.Battle.BattleService.Init"/>, 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
|
||||
/// <c>[LifecycleEvent]</c> marker keeps it out of condition-cached flows.
|
||||
/// </summary>
|
||||
[LifecycleEvent]
|
||||
[EventKey("battle_started")]
|
||||
public class BattleStartedEvent : ModEvent
|
||||
{
|
||||
/// <summary>Logical name of the battle scene route being entered (e.g. <c>"Battle"</c>).</summary>
|
||||
public string BattleType { get; init; }
|
||||
/// <summary>Template ids of the enemies participating in the encounter.</summary>
|
||||
public string[] Enemies { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,11 @@ namespace Cthangover.Core.Events
|
||||
[EventKey("character_died")]
|
||||
public class CharacterDiedEvent : ModEvent
|
||||
{
|
||||
/// <summary>Template id of the character that died.</summary>
|
||||
public string CharacterId { get; init; }
|
||||
/// <summary>Display name at time of death (resolved locale string).</summary>
|
||||
public string CharacterName { get; init; }
|
||||
/// <summary>Free-form id of the reporting battle system.</summary>
|
||||
public string Source { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -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<AudioService>("/root/AudioService");
|
||||
var audioService = GetNodeOrNull<Node>("/root/AudioService") as IAudioService;
|
||||
audioService?.PlaySound("battle/deadground", SoundType.UI);
|
||||
|
||||
var bg = GetNodeOrNull<TextureRect>("TransitionBg");
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Cthangover.Core.Battle
|
||||
{
|
||||
private readonly GodotObject _instance;
|
||||
|
||||
/// <summary>Registry key reported by the script's <c>get_winground_listener_id()</c>; deduplicates on rescan.</summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +32,7 @@ namespace Cthangover.Core.Battle
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
/// <summary>Calls the script's <c>on_winground_before_exp(ctx)</c> if implemented; no-op otherwise.</summary>
|
||||
public void OnBeforeExpAward(WingroundContext ctx)
|
||||
{
|
||||
if (!_instance.HasMethod("on_winground_before_exp"))
|
||||
@@ -41,6 +43,7 @@ namespace Cthangover.Core.Battle
|
||||
BattleGDScriptBridge.SyncWingroundBack(gdCtx, ctx);
|
||||
}
|
||||
|
||||
/// <summary>Calls the script's <c>on_winground_after_loot(ctx)</c> if implemented; no-op otherwise.</summary>
|
||||
public void OnAfterLootGenerated(WingroundContext ctx)
|
||||
{
|
||||
if (!_instance.HasMethod("on_winground_after_loot"))
|
||||
@@ -51,6 +54,7 @@ namespace Cthangover.Core.Battle
|
||||
BattleGDScriptBridge.SyncWingroundBack(gdCtx, ctx);
|
||||
}
|
||||
|
||||
/// <summary>Calls the script's <c>on_winground_before_show(ctx)</c> if implemented; no-op otherwise.</summary>
|
||||
public void OnBeforeShow(WingroundContext ctx)
|
||||
{
|
||||
if (!_instance.HasMethod("on_winground_before_show"))
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Cthangover.Core.Battle
|
||||
public interface IBattleSpeedControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Restores <c>Engine.TimeScale</c> to 1x without saving the preference.
|
||||
/// Restores <c>Godot.Engine.TimeScale</c> 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.
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Cthangover.Core.Factories.Impls
|
||||
#region Singleton
|
||||
|
||||
private static readonly Lazy<StatusEffectActionFactory> instance = new(() => new StatusEffectActionFactory());
|
||||
/// <summary>Lazy singleton; construction performs the AppDomain scan.</summary>
|
||||
public static StatusEffectActionFactory Instance => instance.Value;
|
||||
|
||||
public StatusEffectActionFactory()
|
||||
@@ -28,12 +29,16 @@ namespace Cthangover.Core.Factories.Impls
|
||||
actions = new Dictionary<string, IStatusActions>();
|
||||
foreach (var action in Reflections.FindAndCreate<IStatusActions>())
|
||||
{
|
||||
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
|
||||
|
||||
/// <summary>Resolves the behaviour set registered under the effect's <c>Actions</c> id; unknown ids are logged and return <c>null</c>.</summary>
|
||||
public IStatusActions Get(string id)
|
||||
{
|
||||
if (actions.TryGetValue(id, out var result))
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Cthangover.Core.Factories.Impls
|
||||
public class StatusEffectInfoFactory : FileFactory<StatusEffectInfo>
|
||||
{
|
||||
private static readonly Lazy<StatusEffectInfoFactory> lazy = new(() => new StatusEffectInfoFactory());
|
||||
/// <summary>Lazy singleton facade over the AppServices-owned factory.</summary>
|
||||
public static StatusEffectInfoFactory Instance => lazy.Value;
|
||||
}
|
||||
}
|
||||
@@ -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<AudioService>("/root/AudioService");
|
||||
var audioService = GetNodeOrNull<Node>("/root/AudioService") as IAudioService;
|
||||
audioService?.PlaySound("battle/winground", SoundType.UI);
|
||||
|
||||
var bg = GetNodeOrNull<TextureRect>("TransitionBg");
|
||||
|
||||
Reference in new issue
Block a user