using System; using UnityEngine; namespace Unity.VisualScripting { /// /// Runs a cooldown timer to throttle flow and outputs remaining measurements. /// [UnitCategory("Time")] [TypeIcon(typeof(Timer))] [UnitOrder(8)] public sealed class Cooldown : Unit, IGraphElementWithData, IGraphEventListener { public sealed class Data : IGraphElementData { public float remaining; public float duration; public bool unscaled; public bool isReady => remaining <= 0; public Delegate update; public bool isListening; } /// /// The moment at which to try using the cooldown. /// [DoNotSerialize] [PortLabelHidden] public ControlInput enter { get; private set; } /// /// Trigger to force reset the cooldown. /// [DoNotSerialize] public ControlInput reset { get; private set; } /// /// The total duration of the cooldown. /// [DoNotSerialize] public ValueInput duration { get; private set; } /// /// Whether to ignore the time scale. /// [DoNotSerialize] [PortLabel("Unscaled")] public ValueInput unscaledTime { get; private set; } /// /// Called upon entry when the cooldown is ready. /// [DoNotSerialize] [PortLabel("Ready")] public ControlOutput exitReady { get; private set; } /// /// Called upon entry when the cooldown is not yet ready. /// [DoNotSerialize] [PortLabel("Not Ready")] public ControlOutput exitNotReady { get; private set; } /// /// Called each frame while the cooldown timer is active. /// [DoNotSerialize] public ControlOutput tick { get; private set; } /// /// Called when the cooldown timer reaches zero. /// [DoNotSerialize] [PortLabel("Completed")] public ControlOutput becameReady { get; private set; } /// /// The number of seconds remaining until the cooldown is ready. /// [DoNotSerialize] [PortLabel("Remaining")] public ValueOutput remainingSeconds { get; private set; } /// /// The proportion of the duration remaining until the cooldown is ready (0-1). /// [DoNotSerialize] [PortLabel("Remaining %")] public ValueOutput remainingRatio { get; private set; } protected override void Definition() { enter = ControlInput(nameof(enter), Enter); reset = ControlInput(nameof(reset), Reset); duration = ValueInput(nameof(duration), 1f); unscaledTime = ValueInput(nameof(unscaledTime), false); exitReady = ControlOutput(nameof(exitReady)); exitNotReady = ControlOutput(nameof(exitNotReady)); tick = ControlOutput(nameof(tick)); becameReady = ControlOutput(nameof(becameReady)); remainingSeconds = ValueOutput(nameof(remainingSeconds)); remainingRatio = ValueOutput(nameof(remainingRatio)); Requirement(duration, enter); Requirement(unscaledTime, enter); Succession(enter, exitReady); Succession(enter, exitNotReady); Succession(enter, tick); Succession(enter, becameReady); Assignment(enter, remainingSeconds); Assignment(enter, remainingRatio); } public IGraphElementData CreateData() { return new Data(); } public void StartListening(GraphStack stack) { var data = stack.GetElementData(this); if (data.isListening) { return; } var reference = stack.ToReference(); var hook = new EventHook(EventHooks.Update, stack.machine); Action update = args => TriggerUpdate(reference); EventBus.Register(hook, update); data.update = update; data.isListening = true; } public void StopListening(GraphStack stack) { var data = stack.GetElementData(this); if (!data.isListening) { return; } var hook = new EventHook(EventHooks.Update, stack.machine); EventBus.Unregister(hook, data.update); data.update = null; data.isListening = false; } public bool IsListening(GraphPointer pointer) { return pointer.GetElementData(this).isListening; } private void TriggerUpdate(GraphReference reference) { using (var flow = Flow.New(reference)) { Update(flow); } } private ControlOutput Enter(Flow flow) { var data = flow.stack.GetElementData(this); if (data.isReady) { return Reset(flow); } else { return exitNotReady; } } private ControlOutput Reset(Flow flow) { var data = flow.stack.GetElementData(this); data.duration = flow.GetValue(duration); data.remaining = data.duration; data.unscaled = flow.GetValue(unscaledTime); return exitReady; } private void AssignMetrics(Flow flow, Data data) { flow.SetValue(remainingSeconds, data.remaining); flow.SetValue(remainingRatio, Mathf.Clamp01(data.remaining / data.duration)); } public void Update(Flow flow) { var data = flow.stack.GetElementData(this); if (data.isReady) { return; } data.remaining -= data.unscaled ? Time.unscaledDeltaTime : Time.deltaTime; data.remaining = Mathf.Max(0f, data.remaining); AssignMetrics(flow, data); var stack = flow.PreserveStack(); flow.Invoke(tick); if (data.isReady) { flow.RestoreStack(stack); flow.Invoke(becameReady); } flow.DisposePreservedStack(stack); } } }