com.wallstop-studios.unity-helpers 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Docs/DATA_STRUCTURES.md +7 -7
- package/Docs/EFFECTS_SYSTEM.md +836 -8
- package/Docs/EFFECTS_SYSTEM_TUTORIAL.md +77 -18
- package/Docs/HULLS.md +2 -2
- package/Docs/RANDOM_PERFORMANCE.md +1 -1
- package/Docs/REFLECTION_HELPERS.md +1 -1
- package/Docs/RELATIONAL_COMPONENTS.md +51 -6
- package/Docs/SERIALIZATION.md +1 -1
- package/Docs/SINGLETONS.md +2 -2
- package/Docs/SPATIAL_TREES_2D_GUIDE.md +3 -3
- package/Docs/SPATIAL_TREES_3D_GUIDE.md +3 -3
- package/Docs/SPATIAL_TREE_SEMANTICS.md +7 -7
- package/Editor/CustomDrawers/WShowIfPropertyDrawer.cs +131 -41
- package/Editor/Utils/ScriptableObjectSingletonCreator.cs +175 -18
- package/README.md +17 -3
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +4 -2
- package/Runtime/Tags/Attribute.cs +144 -24
- package/Runtime/Tags/AttributeEffect.cs +278 -11
- package/Runtime/Tags/AttributeModification.cs +59 -29
- package/Runtime/Tags/AttributeUtilities.cs +465 -0
- package/Runtime/Tags/AttributesComponent.cs +20 -0
- package/Runtime/Tags/EffectBehavior.cs +171 -0
- package/Runtime/Tags/EffectBehavior.cs.meta +4 -0
- package/Runtime/Tags/EffectHandle.cs +5 -0
- package/Runtime/Tags/EffectHandler.cs +564 -39
- package/Runtime/Tags/EffectStackKey.cs +79 -0
- package/Runtime/Tags/EffectStackKey.cs.meta +4 -0
- package/Runtime/Tags/PeriodicEffectDefinition.cs +102 -0
- package/Runtime/Tags/PeriodicEffectDefinition.cs.meta +4 -0
- package/Runtime/Tags/PeriodicEffectRuntimeState.cs +40 -0
- package/Runtime/Tags/PeriodicEffectRuntimeState.cs.meta +4 -0
- package/Runtime/Tags/TagHandler.cs +375 -21
- package/Samples~/DI - Zenject/README.md +0 -2
- package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs +285 -0
- package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs.meta +11 -0
- package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +2 -2
- package/Tests/Editor/Utils/ScriptableObjectSingletonTests.cs +41 -0
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +4 -3
- package/Tests/Runtime/Tags/AttributeEffectTests.cs +135 -0
- package/Tests/Runtime/Tags/AttributeEffectTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/AttributeModificationTests.cs +137 -0
- package/Tests/Runtime/Tags/AttributeTests.cs +192 -0
- package/Tests/Runtime/Tags/AttributeTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/AttributeUtilitiesTests.cs +245 -0
- package/Tests/Runtime/Tags/CosmeticAndCollisionTests.cs +1 -1
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs +184 -0
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/EffectHandlerTests.cs +809 -0
- package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs +89 -0
- package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs.meta +4 -0
- package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +92 -0
- package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/TagHandlerTests.cs +130 -6
- package/package.json +1 -1
- package/scripts/lint-doc-links.ps1 +156 -11
- package/Tests/Runtime/Tags/AttributeDataTests.cs +0 -312
- package/node_modules.meta +0 -8
- /package/Tests/Runtime/Tags/{AttributeDataTests.cs.meta → AttributeModificationTests.cs.meta} +0 -0
|
@@ -67,6 +67,9 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
67
67
|
List<CosmeticEffectData>
|
|
68
68
|
> _instancedCosmeticEffects = new();
|
|
69
69
|
|
|
70
|
+
private readonly Dictionary<EffectStackKey, List<EffectHandle>> _handlesByStackKey = new();
|
|
71
|
+
private readonly Dictionary<long, EffectStackKey> _stackKeyByHandleId = new();
|
|
72
|
+
|
|
70
73
|
// Stores expiration time of duration effects (We store by Id because it's much cheaper to iterate Guids than it is EffectHandles
|
|
71
74
|
private readonly Dictionary<long, float> _effectExpirations = new();
|
|
72
75
|
private readonly Dictionary<long, EffectHandle> _effectHandlesById = new();
|
|
@@ -74,6 +77,9 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
74
77
|
// Used only to save allocations in Update()
|
|
75
78
|
private readonly List<long> _expiredEffectIds = new();
|
|
76
79
|
private readonly List<EffectHandle> _appliedEffects = new();
|
|
80
|
+
private readonly Dictionary<long, List<PeriodicEffectRuntimeState>> _periodicEffectStates =
|
|
81
|
+
new();
|
|
82
|
+
private readonly Dictionary<long, List<EffectBehavior>> _behaviorsByHandleId = new();
|
|
77
83
|
|
|
78
84
|
private bool _initialized;
|
|
79
85
|
|
|
@@ -118,51 +124,109 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
118
124
|
/// </remarks>
|
|
119
125
|
public EffectHandle? ApplyEffect(AttributeEffect effect)
|
|
120
126
|
{
|
|
121
|
-
|
|
127
|
+
if (effect == null)
|
|
128
|
+
{
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
122
131
|
|
|
123
|
-
if (effect.durationType
|
|
132
|
+
if (effect.durationType == ModifierDurationType.Instant)
|
|
124
133
|
{
|
|
125
|
-
if (effect
|
|
134
|
+
if (RequiresHandle(effect))
|
|
135
|
+
{
|
|
136
|
+
this.LogWarn(
|
|
137
|
+
$"Effect {effect:json} defines periodic or behaviour data but is Instant. These features require a Duration or Infinite effect."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
InternalApplyEffect(effect);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
EffectStackKey stackKey = effect.GetStackKey();
|
|
146
|
+
List<EffectHandle> existingHandles = TryGetStackHandles(stackKey);
|
|
147
|
+
|
|
148
|
+
switch (effect.stackingMode)
|
|
149
|
+
{
|
|
150
|
+
case EffectStackingMode.Ignore:
|
|
151
|
+
{
|
|
152
|
+
if (existingHandles is { Count: > 0 })
|
|
153
|
+
{
|
|
154
|
+
return existingHandles[0];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case EffectStackingMode.Refresh:
|
|
160
|
+
{
|
|
161
|
+
if (existingHandles is { Count: > 0 })
|
|
162
|
+
{
|
|
163
|
+
EffectHandle handle = existingHandles[0];
|
|
164
|
+
InternalApplyEffect(handle);
|
|
165
|
+
return handle;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case EffectStackingMode.Replace:
|
|
126
171
|
{
|
|
127
|
-
|
|
172
|
+
if (existingHandles is { Count: > 0 })
|
|
128
173
|
{
|
|
129
|
-
|
|
174
|
+
using PooledResource<List<EffectHandle>> handleBufferResource =
|
|
175
|
+
Buffers<EffectHandle>.List.Get(out List<EffectHandle> handleBuffer);
|
|
176
|
+
handleBuffer.AddRange(existingHandles);
|
|
177
|
+
for (int i = 0; i < handleBuffer.Count; ++i)
|
|
130
178
|
{
|
|
131
|
-
|
|
179
|
+
RemoveEffect(handleBuffer[i]);
|
|
132
180
|
}
|
|
181
|
+
}
|
|
133
182
|
|
|
134
|
-
|
|
135
|
-
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case EffectStackingMode.Stack:
|
|
186
|
+
{
|
|
187
|
+
if (existingHandles is { Count: > 0 } && effect.maximumStacks > 0)
|
|
188
|
+
{
|
|
189
|
+
while (existingHandles.Count >= effect.maximumStacks)
|
|
136
190
|
{
|
|
137
|
-
|
|
138
|
-
|
|
191
|
+
EffectHandle oldestHandle = existingHandles[0];
|
|
192
|
+
RemoveEffect(oldestHandle);
|
|
139
193
|
}
|
|
140
194
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
maybeHandle ??= EffectHandle.CreateInstance(effect);
|
|
144
|
-
}
|
|
145
195
|
|
|
146
|
-
|
|
147
|
-
{
|
|
148
|
-
EffectHandle handle = maybeHandle.Value;
|
|
149
|
-
InternalApplyEffect(handle);
|
|
150
|
-
if (
|
|
151
|
-
effect.durationType == ModifierDurationType.Duration
|
|
152
|
-
&& (effect.resetDurationOnReapplication || !_appliedEffects.Contains(handle))
|
|
153
|
-
)
|
|
154
|
-
{
|
|
155
|
-
long handleId = handle.id;
|
|
156
|
-
_effectExpirations[handleId] = Time.time + effect.duration;
|
|
157
|
-
_effectHandlesById[handleId] = handle;
|
|
196
|
+
break;
|
|
158
197
|
}
|
|
159
198
|
}
|
|
160
|
-
|
|
199
|
+
|
|
200
|
+
EffectHandle newHandle = EffectHandle.CreateInstance(effect);
|
|
201
|
+
RegisterStackHandle(stackKey, newHandle);
|
|
202
|
+
InternalApplyEffect(newHandle);
|
|
203
|
+
return newHandle;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private static bool RequiresHandle(AttributeEffect effect)
|
|
207
|
+
{
|
|
208
|
+
return (effect.periodicEffects is { Count: > 0 })
|
|
209
|
+
|| (effect.behaviors is { Count: > 0 });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private List<EffectHandle> TryGetStackHandles(EffectStackKey stackKey)
|
|
213
|
+
{
|
|
214
|
+
_ = _handlesByStackKey.TryGetValue(stackKey, out List<EffectHandle> handles);
|
|
215
|
+
return handles;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private void RegisterStackHandle(EffectStackKey stackKey, EffectHandle handle)
|
|
219
|
+
{
|
|
220
|
+
long handleId = handle.id;
|
|
221
|
+
_stackKeyByHandleId[handleId] = stackKey;
|
|
222
|
+
|
|
223
|
+
if (!_handlesByStackKey.TryGetValue(stackKey, out List<EffectHandle> handles))
|
|
161
224
|
{
|
|
162
|
-
|
|
225
|
+
handles = new List<EffectHandle>();
|
|
226
|
+
_handlesByStackKey.Add(stackKey, handles);
|
|
163
227
|
}
|
|
164
228
|
|
|
165
|
-
|
|
229
|
+
handles.Add(handle);
|
|
166
230
|
}
|
|
167
231
|
|
|
168
232
|
/// <summary>
|
|
@@ -173,17 +237,236 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
173
237
|
{
|
|
174
238
|
InternalRemoveEffect(handle);
|
|
175
239
|
_ = _appliedEffects.Remove(handle);
|
|
240
|
+
DeregisterHandle(handle);
|
|
176
241
|
}
|
|
177
242
|
|
|
178
243
|
public void RemoveAllEffects()
|
|
179
244
|
{
|
|
180
|
-
|
|
245
|
+
using PooledResource<List<EffectHandle>> handleBufferResource =
|
|
246
|
+
Buffers<EffectHandle>.List.Get(out List<EffectHandle> handleBuffer);
|
|
247
|
+
handleBuffer.AddRange(_appliedEffects);
|
|
248
|
+
foreach (EffectHandle handle in handleBuffer)
|
|
181
249
|
{
|
|
182
|
-
|
|
250
|
+
RemoveEffect(handle);
|
|
183
251
|
}
|
|
184
252
|
_appliedEffects.Clear();
|
|
185
253
|
}
|
|
186
254
|
|
|
255
|
+
private void DeregisterHandle(EffectHandle handle)
|
|
256
|
+
{
|
|
257
|
+
long handleId = handle.id;
|
|
258
|
+
if (_stackKeyByHandleId.TryGetValue(handleId, out EffectStackKey stackKey))
|
|
259
|
+
{
|
|
260
|
+
if (_handlesByStackKey.TryGetValue(stackKey, out List<EffectHandle> handles))
|
|
261
|
+
{
|
|
262
|
+
handles.Remove(handle);
|
|
263
|
+
if (handles.Count == 0)
|
|
264
|
+
{
|
|
265
|
+
_handlesByStackKey.Remove(stackKey);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_stackKeyByHandleId.Remove(handleId);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_periodicEffectStates.Remove(handleId);
|
|
273
|
+
|
|
274
|
+
if (_behaviorsByHandleId.Remove(handleId, out List<EffectBehavior> behaviorInstances))
|
|
275
|
+
{
|
|
276
|
+
EffectBehaviorContext context = new(this, handle, 0f);
|
|
277
|
+
for (int i = 0; i < behaviorInstances.Count; ++i)
|
|
278
|
+
{
|
|
279
|
+
EffectBehavior behavior = behaviorInstances[i];
|
|
280
|
+
if (behavior == null)
|
|
281
|
+
{
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
behavior.OnRemove(context);
|
|
286
|
+
Destroy(behavior);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// <summary>
|
|
292
|
+
/// Determines whether the specified effect is currently active on this handler.
|
|
293
|
+
/// </summary>
|
|
294
|
+
/// <param name="effect">The effect to check.</param>
|
|
295
|
+
/// <returns><c>true</c> if at least one handle for the effect is active; otherwise, <c>false</c>.</returns>
|
|
296
|
+
public bool IsEffectActive(AttributeEffect effect)
|
|
297
|
+
{
|
|
298
|
+
if (effect == null)
|
|
299
|
+
{
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (int i = 0; i < _appliedEffects.Count; ++i)
|
|
304
|
+
{
|
|
305
|
+
EffectHandle handle = _appliedEffects[i];
|
|
306
|
+
if (handle.effect == effect)
|
|
307
|
+
{
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// <summary>
|
|
316
|
+
/// Gets the number of active handles for the specified effect.
|
|
317
|
+
/// </summary>
|
|
318
|
+
/// <param name="effect">The effect to count.</param>
|
|
319
|
+
/// <returns>The number of active handles associated with <paramref name="effect"/>.</returns>
|
|
320
|
+
public int GetEffectStackCount(AttributeEffect effect)
|
|
321
|
+
{
|
|
322
|
+
if (effect == null)
|
|
323
|
+
{
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
int count = 0;
|
|
328
|
+
for (int i = 0; i < _appliedEffects.Count; ++i)
|
|
329
|
+
{
|
|
330
|
+
EffectHandle handle = _appliedEffects[i];
|
|
331
|
+
if (handle.effect == effect)
|
|
332
|
+
{
|
|
333
|
+
++count;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return count;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// <summary>
|
|
341
|
+
/// Copies all active effect handles into the provided buffer.
|
|
342
|
+
/// </summary>
|
|
343
|
+
/// <param name="buffer">
|
|
344
|
+
/// Optional list to populate. When <c>null</c>, a new list is created. The buffer is cleared before population.
|
|
345
|
+
/// </param>
|
|
346
|
+
/// <returns>The populated buffer containing all currently active effect handles.</returns>
|
|
347
|
+
public List<EffectHandle> GetActiveEffects(List<EffectHandle> buffer = null)
|
|
348
|
+
{
|
|
349
|
+
buffer ??= new List<EffectHandle>();
|
|
350
|
+
buffer.Clear();
|
|
351
|
+
buffer.AddRange(_appliedEffects);
|
|
352
|
+
return buffer;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// <summary>
|
|
356
|
+
/// Attempts to retrieve the remaining duration for the specified effect handle.
|
|
357
|
+
/// </summary>
|
|
358
|
+
/// <param name="handle">The handle to inspect.</param>
|
|
359
|
+
/// <param name="remainingDuration">When this method returns, contains the remaining time in seconds, or zero if unavailable.</param>
|
|
360
|
+
/// <returns><c>true</c> if the handle has a tracked duration; otherwise, <c>false</c>.</returns>
|
|
361
|
+
public bool TryGetRemainingDuration(EffectHandle handle, out float remainingDuration)
|
|
362
|
+
{
|
|
363
|
+
long handleId = handle.id;
|
|
364
|
+
if (!_effectExpirations.TryGetValue(handleId, out float expiration))
|
|
365
|
+
{
|
|
366
|
+
remainingDuration = 0f;
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
float timeRemaining = expiration - Time.time;
|
|
371
|
+
if (timeRemaining < 0f)
|
|
372
|
+
{
|
|
373
|
+
timeRemaining = 0f;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
remainingDuration = timeRemaining;
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// <summary>
|
|
381
|
+
/// Ensures an effect handle exists for the specified effect, optionally refreshing its duration if already active.
|
|
382
|
+
/// </summary>
|
|
383
|
+
/// <param name="effect">The effect to apply or refresh.</param>
|
|
384
|
+
/// <returns>An active handle for the effect, or <c>null</c> for instant effects.</returns>
|
|
385
|
+
public EffectHandle? EnsureHandle(AttributeEffect effect)
|
|
386
|
+
{
|
|
387
|
+
return EnsureHandle(effect, refreshDuration: true);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/// <summary>
|
|
391
|
+
/// Ensures an effect handle exists for the specified effect, optionally refreshing its duration if already active.
|
|
392
|
+
/// </summary>
|
|
393
|
+
/// <param name="effect">The effect to apply or refresh.</param>
|
|
394
|
+
/// <param name="refreshDuration">
|
|
395
|
+
/// When <c>true</c>, attempts to refresh the effect's duration when it is already active and supports reapplication.
|
|
396
|
+
/// </param>
|
|
397
|
+
/// <returns>An active handle for the effect, or <c>null</c> for instant effects.</returns>
|
|
398
|
+
public EffectHandle? EnsureHandle(AttributeEffect effect, bool refreshDuration)
|
|
399
|
+
{
|
|
400
|
+
if (effect == null)
|
|
401
|
+
{
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (int i = 0; i < _appliedEffects.Count; ++i)
|
|
406
|
+
{
|
|
407
|
+
EffectHandle handle = _appliedEffects[i];
|
|
408
|
+
if (handle.effect == effect)
|
|
409
|
+
{
|
|
410
|
+
if (refreshDuration)
|
|
411
|
+
{
|
|
412
|
+
_ = RefreshEffect(handle);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return handle;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return ApplyEffect(effect);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/// <summary>
|
|
423
|
+
/// Attempts to refresh the duration of the specified effect handle.
|
|
424
|
+
/// </summary>
|
|
425
|
+
/// <param name="handle">The handle to refresh.</param>
|
|
426
|
+
/// <returns><c>true</c> if the duration was refreshed; otherwise, <c>false</c>.</returns>
|
|
427
|
+
public bool RefreshEffect(EffectHandle handle)
|
|
428
|
+
{
|
|
429
|
+
return RefreshEffect(handle, ignoreReapplicationPolicy: false);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/// <summary>
|
|
433
|
+
/// Attempts to refresh the duration of the specified effect handle.
|
|
434
|
+
/// </summary>
|
|
435
|
+
/// <param name="handle">The handle to refresh.</param>
|
|
436
|
+
/// <param name="ignoreReapplicationPolicy">
|
|
437
|
+
/// When <c>true</c>, refreshes the duration even if <see cref="AttributeEffect.resetDurationOnReapplication"/> is <c>false</c>.
|
|
438
|
+
/// </param>
|
|
439
|
+
/// <returns><c>true</c> if the duration was refreshed; otherwise, <c>false</c>.</returns>
|
|
440
|
+
public bool RefreshEffect(EffectHandle handle, bool ignoreReapplicationPolicy)
|
|
441
|
+
{
|
|
442
|
+
AttributeEffect effect = handle.effect;
|
|
443
|
+
if (effect == null)
|
|
444
|
+
{
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (effect.durationType != ModifierDurationType.Duration)
|
|
449
|
+
{
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!ignoreReapplicationPolicy && !effect.resetDurationOnReapplication)
|
|
454
|
+
{
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
long handleId = handle.id;
|
|
459
|
+
if (!_effectExpirations.ContainsKey(handleId))
|
|
460
|
+
{
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
float newExpiration = Time.time + effect.duration;
|
|
465
|
+
_effectExpirations[handleId] = newExpiration;
|
|
466
|
+
_effectHandlesById[handleId] = handle;
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
|
|
187
470
|
private void InternalRemoveEffect(EffectHandle handle)
|
|
188
471
|
{
|
|
189
472
|
foreach (AttributesComponent attributesComponent in _attributes)
|
|
@@ -217,17 +500,24 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
217
500
|
_appliedEffects.Add(handle);
|
|
218
501
|
}
|
|
219
502
|
|
|
503
|
+
long handleId = handle.id;
|
|
504
|
+
_effectHandlesById[handleId] = handle;
|
|
505
|
+
|
|
220
506
|
AttributeEffect effect = handle.effect;
|
|
221
507
|
if (effect.durationType == ModifierDurationType.Duration)
|
|
222
508
|
{
|
|
223
|
-
if (effect.resetDurationOnReapplication
|
|
509
|
+
if (!exists || effect.resetDurationOnReapplication)
|
|
224
510
|
{
|
|
225
|
-
long handleId = handle.id;
|
|
226
511
|
_effectExpirations[handleId] = Time.time + effect.duration;
|
|
227
|
-
_effectHandlesById[handleId] = handle;
|
|
228
512
|
}
|
|
229
513
|
}
|
|
230
514
|
|
|
515
|
+
if (!exists)
|
|
516
|
+
{
|
|
517
|
+
RegisterPeriodicRuntime(handle);
|
|
518
|
+
RegisterBehaviors(handle);
|
|
519
|
+
}
|
|
520
|
+
|
|
231
521
|
if (!_initialized && _tagHandler == null)
|
|
232
522
|
{
|
|
233
523
|
this.AssignRelationalComponents();
|
|
@@ -256,6 +546,13 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
256
546
|
|
|
257
547
|
private void InternalApplyEffect(AttributeEffect effect)
|
|
258
548
|
{
|
|
549
|
+
if (effect.durationType == ModifierDurationType.Instant && RequiresHandle(effect))
|
|
550
|
+
{
|
|
551
|
+
this.LogWarn(
|
|
552
|
+
$"Effect {effect:json} defines periodic or behaviour data but is Instant. These features require a Duration or Infinite effect."
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
259
556
|
if (!_initialized && _tagHandler == null)
|
|
260
557
|
{
|
|
261
558
|
this.AssignRelationalComponents();
|
|
@@ -280,6 +577,116 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
280
577
|
}
|
|
281
578
|
}
|
|
282
579
|
|
|
580
|
+
private void RegisterPeriodicRuntime(EffectHandle handle)
|
|
581
|
+
{
|
|
582
|
+
AttributeEffect effect = handle.effect;
|
|
583
|
+
if (effect.periodicEffects is not { Count: > 0 })
|
|
584
|
+
{
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
List<PeriodicEffectRuntimeState> runtimeStates = null;
|
|
589
|
+
float startTime = Time.time;
|
|
590
|
+
|
|
591
|
+
foreach (PeriodicEffectDefinition definition in effect.periodicEffects)
|
|
592
|
+
{
|
|
593
|
+
if (definition == null)
|
|
594
|
+
{
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
(runtimeStates ??= new List<PeriodicEffectRuntimeState>()).Add(
|
|
599
|
+
new PeriodicEffectRuntimeState(definition, startTime)
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (runtimeStates is { Count: > 0 })
|
|
604
|
+
{
|
|
605
|
+
_periodicEffectStates[handle.id] = runtimeStates;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private void RegisterBehaviors(EffectHandle handle)
|
|
610
|
+
{
|
|
611
|
+
AttributeEffect effect = handle.effect;
|
|
612
|
+
if (effect.behaviors is not { Count: > 0 })
|
|
613
|
+
{
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
List<EffectBehavior> instances = null;
|
|
618
|
+
foreach (EffectBehavior behavior in effect.behaviors)
|
|
619
|
+
{
|
|
620
|
+
if (behavior == null)
|
|
621
|
+
{
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
EffectBehavior clone = Instantiate(behavior);
|
|
626
|
+
(instances ??= new List<EffectBehavior>()).Add(clone);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (instances is not { Count: > 0 })
|
|
630
|
+
{
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
EffectBehaviorContext context = new(this, handle, 0f);
|
|
635
|
+
for (int i = 0; i < instances.Count; ++i)
|
|
636
|
+
{
|
|
637
|
+
EffectBehavior instance = instances[i];
|
|
638
|
+
if (instance == null)
|
|
639
|
+
{
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
instance.OnApply(context);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
_behaviorsByHandleId[handle.id] = instances;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private void ApplyPeriodicTick(
|
|
650
|
+
EffectHandle handle,
|
|
651
|
+
PeriodicEffectRuntimeState runtimeState,
|
|
652
|
+
float currentTime,
|
|
653
|
+
float deltaTime
|
|
654
|
+
)
|
|
655
|
+
{
|
|
656
|
+
PeriodicEffectDefinition definition = runtimeState.definition;
|
|
657
|
+
if (_attributes is { Count: > 0 } && definition.modifications is { Count: > 0 })
|
|
658
|
+
{
|
|
659
|
+
foreach (AttributesComponent attributesComponent in _attributes)
|
|
660
|
+
{
|
|
661
|
+
attributesComponent.ApplyAttributeModifications(definition.modifications, null);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (
|
|
666
|
+
_behaviorsByHandleId.TryGetValue(handle.id, out List<EffectBehavior> behaviors)
|
|
667
|
+
&& behaviors.Count > 0
|
|
668
|
+
)
|
|
669
|
+
{
|
|
670
|
+
EffectBehaviorContext context = new(this, handle, deltaTime);
|
|
671
|
+
PeriodicEffectTickContext tickContext = new(
|
|
672
|
+
definition,
|
|
673
|
+
runtimeState.ExecutedTicks,
|
|
674
|
+
currentTime
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
for (int i = 0; i < behaviors.Count; ++i)
|
|
678
|
+
{
|
|
679
|
+
EffectBehavior behavior = behaviors[i];
|
|
680
|
+
if (behavior == null)
|
|
681
|
+
{
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
behavior.OnPeriodicTick(context, tickContext);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
283
690
|
private void InternalApplyCosmeticEffects(EffectHandle handle)
|
|
284
691
|
{
|
|
285
692
|
if (_instancedCosmeticEffects.ContainsKey(handle))
|
|
@@ -332,8 +739,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
332
739
|
{
|
|
333
740
|
foreach (CosmeticEffectData cosmeticEffectData in attributeEffect.cosmeticEffects)
|
|
334
741
|
{
|
|
335
|
-
|
|
336
|
-
if (cosmeticEffect == null)
|
|
742
|
+
if (cosmeticEffectData == null)
|
|
337
743
|
{
|
|
338
744
|
this.LogError(
|
|
339
745
|
$"CosmeticEffectData is null for effect {attributeEffect:json}, cannot determine instancing scheme."
|
|
@@ -353,7 +759,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
353
759
|
Buffers<CosmeticEffectComponent>.List.Get();
|
|
354
760
|
List<CosmeticEffectComponent> cosmeticEffectsBuffer =
|
|
355
761
|
cosmeticEffectsResource.resource;
|
|
356
|
-
|
|
762
|
+
cosmeticEffectData.GetComponents(cosmeticEffectsBuffer);
|
|
357
763
|
foreach (CosmeticEffectComponent cosmeticComponent in cosmeticEffectsBuffer)
|
|
358
764
|
{
|
|
359
765
|
cosmeticComponent.OnApplyEffect(gameObject);
|
|
@@ -442,6 +848,13 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
442
848
|
}
|
|
443
849
|
|
|
444
850
|
private void Update()
|
|
851
|
+
{
|
|
852
|
+
ProcessEffectExpirations();
|
|
853
|
+
ProcessBehaviorTicks();
|
|
854
|
+
ProcessPeriodicEffects();
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
private void ProcessEffectExpirations()
|
|
445
858
|
{
|
|
446
859
|
if (_effectExpirations.Count <= 0)
|
|
447
860
|
{
|
|
@@ -452,19 +865,131 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
452
865
|
float currentTime = Time.time;
|
|
453
866
|
foreach (KeyValuePair<long, float> entry in _effectExpirations)
|
|
454
867
|
{
|
|
455
|
-
if (entry.Value
|
|
868
|
+
if (entry.Value <= currentTime)
|
|
456
869
|
{
|
|
457
870
|
_expiredEffectIds.Add(entry.Key);
|
|
458
871
|
}
|
|
459
872
|
}
|
|
460
873
|
|
|
461
|
-
|
|
874
|
+
for (int i = 0; i < _expiredEffectIds.Count; ++i)
|
|
462
875
|
{
|
|
876
|
+
long expiredHandleId = _expiredEffectIds[i];
|
|
463
877
|
if (_effectHandlesById.TryGetValue(expiredHandleId, out EffectHandle expiredHandle))
|
|
464
878
|
{
|
|
465
879
|
RemoveEffect(expiredHandle);
|
|
466
880
|
}
|
|
467
881
|
}
|
|
882
|
+
|
|
883
|
+
_expiredEffectIds.Clear();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
private void ProcessBehaviorTicks()
|
|
887
|
+
{
|
|
888
|
+
if (_behaviorsByHandleId.Count <= 0)
|
|
889
|
+
{
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
using PooledResource<List<long>> behaviorHandleIdsResource = Buffers<long>.List.Get(
|
|
894
|
+
out List<long> behaviorHandleIdsBuffer
|
|
895
|
+
);
|
|
896
|
+
behaviorHandleIdsBuffer.AddRange(_behaviorsByHandleId.Keys);
|
|
897
|
+
float deltaTime = Time.deltaTime;
|
|
898
|
+
|
|
899
|
+
for (int i = 0; i < behaviorHandleIdsBuffer.Count; ++i)
|
|
900
|
+
{
|
|
901
|
+
long handleId = behaviorHandleIdsBuffer[i];
|
|
902
|
+
if (!_effectHandlesById.TryGetValue(handleId, out EffectHandle handle))
|
|
903
|
+
{
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (!_behaviorsByHandleId.TryGetValue(handleId, out List<EffectBehavior> behaviors))
|
|
908
|
+
{
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
EffectBehaviorContext context = new(this, handle, deltaTime);
|
|
913
|
+
for (int j = 0; j < behaviors.Count; ++j)
|
|
914
|
+
{
|
|
915
|
+
EffectBehavior behavior = behaviors[j];
|
|
916
|
+
if (behavior == null)
|
|
917
|
+
{
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
behavior.OnTick(context);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
private void ProcessPeriodicEffects()
|
|
927
|
+
{
|
|
928
|
+
if (_periodicEffectStates.Count <= 0)
|
|
929
|
+
{
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
float currentTime = Time.time;
|
|
934
|
+
float deltaTime = Time.deltaTime;
|
|
935
|
+
using PooledResource<List<long>> periodicRemovalResource = Buffers<long>.List.Get(
|
|
936
|
+
out List<long> periodicRemovalBuffer
|
|
937
|
+
);
|
|
938
|
+
using PooledResource<List<long>> periodHandleIdsResource = Buffers<long>.List.Get(
|
|
939
|
+
out List<long> periodicHandleIdsBuffer
|
|
940
|
+
);
|
|
941
|
+
periodicHandleIdsBuffer.AddRange(_periodicEffectStates.Keys);
|
|
942
|
+
|
|
943
|
+
for (int handleIndex = 0; handleIndex < periodicHandleIdsBuffer.Count; ++handleIndex)
|
|
944
|
+
{
|
|
945
|
+
long handleId = periodicHandleIdsBuffer[handleIndex];
|
|
946
|
+
if (!_effectHandlesById.TryGetValue(handleId, out EffectHandle handle))
|
|
947
|
+
{
|
|
948
|
+
periodicRemovalBuffer.Add(handleId);
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (
|
|
953
|
+
!_periodicEffectStates.TryGetValue(
|
|
954
|
+
handleId,
|
|
955
|
+
out List<PeriodicEffectRuntimeState> runtimes
|
|
956
|
+
)
|
|
957
|
+
)
|
|
958
|
+
{
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
bool hasActive = false;
|
|
963
|
+
|
|
964
|
+
for (int i = 0; i < runtimes.Count; ++i)
|
|
965
|
+
{
|
|
966
|
+
PeriodicEffectRuntimeState runtimeState = runtimes[i];
|
|
967
|
+
if (runtimeState == null)
|
|
968
|
+
{
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
while (runtimeState.TryConsumeTick(currentTime))
|
|
973
|
+
{
|
|
974
|
+
ApplyPeriodicTick(handle, runtimeState, currentTime, deltaTime);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (!runtimeState.IsComplete)
|
|
978
|
+
{
|
|
979
|
+
hasActive = true;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (!hasActive)
|
|
984
|
+
{
|
|
985
|
+
periodicRemovalBuffer.Add(handleId);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
for (int i = 0; i < periodicRemovalBuffer.Count; ++i)
|
|
990
|
+
{
|
|
991
|
+
_periodicEffectStates.Remove(periodicRemovalBuffer[i]);
|
|
992
|
+
}
|
|
468
993
|
}
|
|
469
994
|
}
|
|
470
995
|
}
|