A game about forced loneliness, made by TACStudios
1using System; 2using UnityEngine; 3 4namespace Unity.VisualScripting 5{ 6 /// <summary> 7 /// Runs a cooldown timer to throttle flow and outputs remaining measurements. 8 /// </summary> 9 [UnitCategory("Time")] 10 [TypeIcon(typeof(Timer))] 11 [UnitOrder(8)] 12 public sealed class Cooldown : Unit, IGraphElementWithData, IGraphEventListener 13 { 14 public sealed class Data : IGraphElementData 15 { 16 public float remaining; 17 18 public float duration; 19 20 public bool unscaled; 21 22 public bool isReady => remaining <= 0; 23 24 public Delegate update; 25 26 public bool isListening; 27 } 28 29 /// <summary> 30 /// The moment at which to try using the cooldown. 31 /// </summary> 32 [DoNotSerialize] 33 [PortLabelHidden] 34 public ControlInput enter { get; private set; } 35 36 /// <summary> 37 /// Trigger to force reset the cooldown. 38 /// </summary> 39 [DoNotSerialize] 40 public ControlInput reset { get; private set; } 41 42 /// <summary> 43 /// The total duration of the cooldown. 44 /// </summary> 45 [DoNotSerialize] 46 public ValueInput duration { get; private set; } 47 48 /// <summary> 49 /// Whether to ignore the time scale. 50 /// </summary> 51 [DoNotSerialize] 52 [PortLabel("Unscaled")] 53 public ValueInput unscaledTime { get; private set; } 54 55 /// <summary> 56 /// Called upon entry when the cooldown is ready. 57 /// </summary> 58 [DoNotSerialize] 59 [PortLabel("Ready")] 60 public ControlOutput exitReady { get; private set; } 61 62 /// <summary> 63 /// Called upon entry when the cooldown is not yet ready. 64 /// </summary> 65 [DoNotSerialize] 66 [PortLabel("Not Ready")] 67 public ControlOutput exitNotReady { get; private set; } 68 69 /// <summary> 70 /// Called each frame while the cooldown timer is active. 71 /// </summary> 72 [DoNotSerialize] 73 public ControlOutput tick { get; private set; } 74 75 /// <summary> 76 /// Called when the cooldown timer reaches zero. 77 /// </summary> 78 [DoNotSerialize] 79 [PortLabel("Completed")] 80 public ControlOutput becameReady { get; private set; } 81 82 /// <summary> 83 /// The number of seconds remaining until the cooldown is ready. 84 /// </summary> 85 [DoNotSerialize] 86 [PortLabel("Remaining")] 87 public ValueOutput remainingSeconds { get; private set; } 88 89 /// <summary> 90 /// The proportion of the duration remaining until the cooldown is ready (0-1). 91 /// </summary> 92 [DoNotSerialize] 93 [PortLabel("Remaining %")] 94 public ValueOutput remainingRatio { get; private set; } 95 96 protected override void Definition() 97 { 98 enter = ControlInput(nameof(enter), Enter); 99 reset = ControlInput(nameof(reset), Reset); 100 101 duration = ValueInput(nameof(duration), 1f); 102 unscaledTime = ValueInput(nameof(unscaledTime), false); 103 104 exitReady = ControlOutput(nameof(exitReady)); 105 exitNotReady = ControlOutput(nameof(exitNotReady)); 106 tick = ControlOutput(nameof(tick)); 107 becameReady = ControlOutput(nameof(becameReady)); 108 109 remainingSeconds = ValueOutput<float>(nameof(remainingSeconds)); 110 remainingRatio = ValueOutput<float>(nameof(remainingRatio)); 111 112 Requirement(duration, enter); 113 Requirement(unscaledTime, enter); 114 Succession(enter, exitReady); 115 Succession(enter, exitNotReady); 116 Succession(enter, tick); 117 Succession(enter, becameReady); 118 Assignment(enter, remainingSeconds); 119 Assignment(enter, remainingRatio); 120 } 121 122 public IGraphElementData CreateData() 123 { 124 return new Data(); 125 } 126 127 public void StartListening(GraphStack stack) 128 { 129 var data = stack.GetElementData<Data>(this); 130 131 if (data.isListening) 132 { 133 return; 134 } 135 136 var reference = stack.ToReference(); 137 var hook = new EventHook(EventHooks.Update, stack.machine); 138 Action<EmptyEventArgs> update = args => TriggerUpdate(reference); 139 EventBus.Register(hook, update); 140 data.update = update; 141 data.isListening = true; 142 } 143 144 public void StopListening(GraphStack stack) 145 { 146 var data = stack.GetElementData<Data>(this); 147 148 if (!data.isListening) 149 { 150 return; 151 } 152 153 var hook = new EventHook(EventHooks.Update, stack.machine); 154 EventBus.Unregister(hook, data.update); 155 156 stack.ClearReference(); 157 158 data.update = null; 159 data.isListening = false; 160 } 161 162 public bool IsListening(GraphPointer pointer) 163 { 164 return pointer.GetElementData<Data>(this).isListening; 165 } 166 167 private void TriggerUpdate(GraphReference reference) 168 { 169 using (var flow = Flow.New(reference)) 170 { 171 Update(flow); 172 } 173 } 174 175 private ControlOutput Enter(Flow flow) 176 { 177 var data = flow.stack.GetElementData<Data>(this); 178 179 if (data.isReady) 180 { 181 return Reset(flow); 182 } 183 else 184 { 185 return exitNotReady; 186 } 187 } 188 189 private ControlOutput Reset(Flow flow) 190 { 191 var data = flow.stack.GetElementData<Data>(this); 192 193 data.duration = flow.GetValue<float>(duration); 194 data.remaining = data.duration; 195 data.unscaled = flow.GetValue<bool>(unscaledTime); 196 197 return exitReady; 198 } 199 200 private void AssignMetrics(Flow flow, Data data) 201 { 202 flow.SetValue(remainingSeconds, data.remaining); 203 flow.SetValue(remainingRatio, Mathf.Clamp01(data.remaining / data.duration)); 204 } 205 206 public void Update(Flow flow) 207 { 208 var data = flow.stack.GetElementData<Data>(this); 209 210 if (data.isReady) 211 { 212 return; 213 } 214 215 data.remaining -= data.unscaled ? Time.unscaledDeltaTime : Time.deltaTime; 216 217 data.remaining = Mathf.Max(0f, data.remaining); 218 219 AssignMetrics(flow, data); 220 221 var stack = flow.PreserveStack(); 222 223 flow.Invoke(tick); 224 225 if (data.isReady) 226 { 227 flow.RestoreStack(stack); 228 229 flow.Invoke(becameReady); 230 } 231 232 flow.DisposePreservedStack(stack); 233 } 234 } 235}