using Cthangover.Core.UI.Event; using Godot; using Cthangover.Core.UI; using Cthangover.Mods.Interface.Base.Lists; using Cthangover.Core.UI.Dialog; using Cthangover.Core.UI.Executable; using Cthangover.Core.Scenarios; using Cthangover.Core.Interactive; namespace Cthangover.Mods.Dialog.Action.Impls { /// /// Timeline-based update action: registers itself as an IOnUpdateEvent /// 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 /// completes itself through (event-driven — /// no DialogBox poll) and the box advances the queue, scheduling the timer of a /// following delay 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 Engine.TimeScale exactly like delay timers). /// public class ActionTimedUpdate : ActionCommand, IOnUpdateEvent { /// Update priority. Value 0 — runs before most other listeners. public int Priority => 0; /// Fires once when is called, before the first update frame. public System.Action OnStartCallback { get; set; } /// Fires each frame during the active period with a normalized progress value t (0→1) over seconds. public System.Action OnUpdateCallback { get; set; } /// Fires once when the elapsed time exceeds or when destructed. public System.Action OnFinishCallback { get; set; } /// Pauses the dialog until the elapsed time meets WaitTime — the action then completes itself. public override WaitType WaitType { get; set; } = WaitType.WaitEvent; /// Total duration in seconds over which receives normalized progress. public override float WaitTime { get; set; } = 1f; private float elapsed; private bool isRunning; private DialogRuntime runtime; private SceneEventController controller; /// Registers this action as an listener on the scene's event controller. public override void DoConstruct() { base.DoConstruct(); controller = Scene.Find("EventController"); controller?.AddUpdateEventListener(this); } /// Starts the timeline: resets elapsed time and fires before the first frame. public override void DoRun(DialogRuntime runtime) { this.runtime = runtime; elapsed = 0f; isRunning = true; OnStartCallback?.Invoke(); } /// Detaches from the update controller, stops ticking and fires . public override void DoDestruct() { base.DoDestruct(); isRunning = false; runtime = null; controller?.RemoveUpdateEventListener(this); controller = null; OnFinishCallback?.Invoke(); } void IOnUpdateEvent.OnUpdate() { if (!isRunning) return; elapsed += (float)(controller?.GetProcessDeltaTime() ?? 0d); var t = WaitTime > 0f ? Mathf.Min(elapsed / WaitTime, 1f) : 1f; OnUpdateCallback?.Invoke(t); 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) { owner.DialogBox.CompleteAction(this); } else if (owner != null) { owner.Next(); owner.Run(); } } } }