Event-driven dialog queue, story flag command, mod unit tests

Push-based event chains, runtime fixes (single end, goto lifecycle, if,
busy box, held delay, empty options, timed/animated/effect actions), the
'flag name value' DSL command, autoplay --dialogs=N, 19 mod unit tests.
This commit is contained in:
cthangover committed 2026-10-21 12:31:52 +02:00
1 parent 9f86bdbb0c
commit bd55716349
86 files changed
+3173 -2181

No files matched your search

+25 -15
View File
@@ -14,10 +14,12 @@ namespace Cthangover.Mods.Dialog.Action.Impls
/// subscriber and fires OnUpdateCallback(float t) each frame with a normalized
/// progress value (0→1) over WaitTime seconds. WaitType is WaitEvent so the
/// dialog runtime pauses until elapsed >= WaitTime, at which point the action
/// advances the runtime directly (event-driven — no DialogBox poll) and
/// destructs itself. OnStartCallback/OnFinishCallback bracket the animation
/// for setup/teardown. Fixed 60fps step for deterministic progress regardless
/// of actual frame rate.
/// completes itself through <see cref="DialogBox.CompleteAction"/> (event-driven —
/// no DialogBox poll) and the box advances the queue, scheduling the timer of a
/// following <c>delay</c> like any other advance. OnStartCallback/OnFinishCallback
/// bracket the animation for setup/teardown. Progress follows the controller's
/// process delta, so the timeline lasts WaitTime seconds of game time at any frame
/// rate (and scales with <c>Engine.TimeScale</c> exactly like <c>delay</c> timers).
/// </summary>
public class ActionTimedUpdate : ActionCommand, IOnUpdateEvent
{
@@ -31,7 +33,7 @@ namespace Cthangover.Mods.Dialog.Action.Impls
/// <summary>Fires once when the elapsed time exceeds <see cref="WaitTime"/> or when destructed.</summary>
public System.Action OnFinishCallback { get; set; }
/// <summary>Pauses the dialog until the elapsed time meets WaitTime — the action then advances the runtime itself.</summary>
/// <summary>Pauses the dialog until the elapsed time meets WaitTime — the action then completes itself.</summary>
public override WaitType WaitType { get; set; } = WaitType.WaitEvent;
/// <summary>Total duration in seconds over which <see cref="OnUpdateCallback"/> receives normalized progress.</summary>
public override float WaitTime { get; set; } = 1f;
@@ -39,12 +41,13 @@ namespace Cthangover.Mods.Dialog.Action.Impls
private float elapsed;
private bool isRunning;
private DialogRuntime runtime;
private SceneEventController controller;
/// <summary>Registers this action as an <see cref="IOnUpdateEvent"/> listener on the scene's event controller.</summary>
public override void DoConstruct()
{
base.DoConstruct();
var controller = Scene.Find<SceneEventController>("EventController");
controller = Scene.Find<SceneEventController>("EventController");
controller?.AddUpdateEventListener(this);
}
@@ -63,8 +66,8 @@ namespace Cthangover.Mods.Dialog.Action.Impls
base.DoDestruct();
isRunning = false;
runtime = null;
var controller = Scene.Find<SceneEventController>("EventController");
controller?.RemoveUpdateEventListener(this);
controller = null;
OnFinishCallback?.Invoke();
}
@@ -73,19 +76,26 @@ namespace Cthangover.Mods.Dialog.Action.Impls
if (!isRunning)
return;
elapsed += 1f / 60f;
elapsed += (float)(controller?.GetProcessDeltaTime() ?? 0d);
var t = WaitTime > 0f ? Mathf.Min(elapsed / WaitTime, 1f) : 1f;
OnUpdateCallback?.Invoke(t);
if (elapsed >= WaitTime)
if (elapsed < WaitTime)
return;
// Advancing destructs this action, which clears the runtime field:
// keep the runtime in a local for the whole completion.
isRunning = false;
var owner = runtime;
if (owner?.DialogBox != null)
{
// Advances the runtime directly instead of relying on a DialogBox poll.
// Next() destructs this action (firing OnFinishCallback and detaching the
// update listener), then Run() continues the queue past the WaitEvent point.
isRunning = false;
runtime?.Next();
runtime?.Run();
owner.DialogBox.CompleteAction(this);
}
else if (owner != null)
{
owner.Next();
owner.Run();
}
}
}