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}