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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tags
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using WallstopStudios.UnityHelpers.Core.Helper;
|
|
5
|
+
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// Key used to group effect handles for stacking decisions.
|
|
8
|
+
/// </summary>
|
|
9
|
+
internal readonly struct EffectStackKey : IEquatable<EffectStackKey>
|
|
10
|
+
{
|
|
11
|
+
private readonly EffectStackGroup _group;
|
|
12
|
+
private readonly AttributeEffect _effect;
|
|
13
|
+
private readonly string _customKey;
|
|
14
|
+
|
|
15
|
+
private EffectStackKey(EffectStackGroup group, AttributeEffect effect, string customKey)
|
|
16
|
+
{
|
|
17
|
+
_group = group;
|
|
18
|
+
_effect = effect;
|
|
19
|
+
_customKey = customKey;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static EffectStackKey CreateReference(AttributeEffect effect)
|
|
23
|
+
{
|
|
24
|
+
return new EffectStackKey(EffectStackGroup.Reference, effect, null);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static EffectStackKey CreateCustom(string customKey)
|
|
28
|
+
{
|
|
29
|
+
return new EffectStackKey(EffectStackGroup.CustomKey, null, customKey);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public bool Equals(EffectStackKey other)
|
|
33
|
+
{
|
|
34
|
+
if (_group != other._group)
|
|
35
|
+
{
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return _group switch
|
|
40
|
+
{
|
|
41
|
+
EffectStackGroup.Reference => ReferenceEquals(_effect, other._effect),
|
|
42
|
+
EffectStackGroup.CustomKey => string.Equals(
|
|
43
|
+
_customKey,
|
|
44
|
+
other._customKey,
|
|
45
|
+
StringComparison.Ordinal
|
|
46
|
+
),
|
|
47
|
+
_ => false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public override bool Equals(object obj)
|
|
52
|
+
{
|
|
53
|
+
return obj is EffectStackKey other && Equals(other);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public override int GetHashCode()
|
|
57
|
+
{
|
|
58
|
+
return _group switch
|
|
59
|
+
{
|
|
60
|
+
EffectStackGroup.Reference => Objects.HashCode(_group, _effect),
|
|
61
|
+
EffectStackGroup.CustomKey => Objects.HashCode(
|
|
62
|
+
_group,
|
|
63
|
+
_customKey != null ? StringComparer.Ordinal.GetHashCode(_customKey) : 0
|
|
64
|
+
),
|
|
65
|
+
_ => Objects.HashCode(_group),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public static bool operator ==(EffectStackKey left, EffectStackKey right)
|
|
70
|
+
{
|
|
71
|
+
return left.Equals(right);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public static bool operator !=(EffectStackKey left, EffectStackKey right)
|
|
75
|
+
{
|
|
76
|
+
return !(left == right);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tags
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using ProtoBuf;
|
|
6
|
+
using UnityEngine;
|
|
7
|
+
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Authoring data for a periodic modifier bundle that executes on a cadence while an effect is active.
|
|
10
|
+
/// </summary>
|
|
11
|
+
/// <remarks>
|
|
12
|
+
/// <para>
|
|
13
|
+
/// The owning <see cref="EffectHandler"/> evaluates each periodic definition after <see cref="initialDelay"/>, applies the configured
|
|
14
|
+
/// <see cref="modifications"/>, and repeats every <see cref="interval"/> seconds until <see cref="maxTicks"/> is reached or the effect ends.
|
|
15
|
+
/// </para>
|
|
16
|
+
/// <para>
|
|
17
|
+
/// Definitions are processed in list order and maintain independent runtime state per <see cref="EffectHandle"/>, enabling designers to mix
|
|
18
|
+
/// damage-over-time, heal-over-time, or custom triggers alongside bespoke <see cref="EffectBehavior"/> implementations.
|
|
19
|
+
/// </para>
|
|
20
|
+
/// </remarks>
|
|
21
|
+
/// <example>
|
|
22
|
+
/// <code language="csharp">
|
|
23
|
+
/// using System.Collections.Generic;
|
|
24
|
+
/// using UnityEngine;
|
|
25
|
+
/// using WallstopStudios.UnityHelpers.Tags;
|
|
26
|
+
///
|
|
27
|
+
/// public sealed class BurnEffectAuthoring : MonoBehaviour
|
|
28
|
+
/// {
|
|
29
|
+
/// [SerializeField]
|
|
30
|
+
/// private AttributeEffect burnEffect;
|
|
31
|
+
///
|
|
32
|
+
/// [SerializeField]
|
|
33
|
+
/// private EffectHandler effectHandler;
|
|
34
|
+
///
|
|
35
|
+
/// public void ApplyBurn(GameObject target)
|
|
36
|
+
/// {
|
|
37
|
+
/// PeriodicEffectDefinition burnTick = new PeriodicEffectDefinition
|
|
38
|
+
/// {
|
|
39
|
+
/// name = "Burn Damage",
|
|
40
|
+
/// initialDelay = 0.5f,
|
|
41
|
+
/// interval = 1.0f,
|
|
42
|
+
/// maxTicks = 5,
|
|
43
|
+
/// modifications = new List<AttributeModification>
|
|
44
|
+
/// {
|
|
45
|
+
/// new AttributeModification("Health", ModificationAction.Addition, -5f),
|
|
46
|
+
/// },
|
|
47
|
+
/// };
|
|
48
|
+
///
|
|
49
|
+
/// burnEffect.periodicEffects.Add(burnTick);
|
|
50
|
+
///
|
|
51
|
+
/// if (effectHandler == null)
|
|
52
|
+
/// {
|
|
53
|
+
/// effectHandler = target.GetComponent<EffectHandler>();
|
|
54
|
+
/// }
|
|
55
|
+
///
|
|
56
|
+
/// EffectHandle? handle = effectHandler.ApplyEffect(burnEffect);
|
|
57
|
+
/// }
|
|
58
|
+
/// }
|
|
59
|
+
/// </code>
|
|
60
|
+
/// <para>
|
|
61
|
+
/// In the example above the handler waits for <c>initialDelay</c>, applies the <see cref="modifications"/> every <c>interval</c> seconds,
|
|
62
|
+
/// and stops after <see cref="maxTicks"/> executions or as soon as the effect is removed.
|
|
63
|
+
/// </para>
|
|
64
|
+
/// </example>
|
|
65
|
+
[Serializable]
|
|
66
|
+
[ProtoContract]
|
|
67
|
+
public sealed class PeriodicEffectDefinition
|
|
68
|
+
{
|
|
69
|
+
/// <summary>
|
|
70
|
+
/// Optional label shown in tooling to help distinguish multiple periodic definitions.
|
|
71
|
+
/// </summary>
|
|
72
|
+
[ProtoMember(1)]
|
|
73
|
+
public string name;
|
|
74
|
+
|
|
75
|
+
/// <summary>
|
|
76
|
+
/// Time (seconds) before the first tick fires after the effect is applied.
|
|
77
|
+
/// </summary>
|
|
78
|
+
[Min(0f)]
|
|
79
|
+
[ProtoMember(2)]
|
|
80
|
+
public float initialDelay;
|
|
81
|
+
|
|
82
|
+
/// <summary>
|
|
83
|
+
/// Interval (seconds) between ticks once the first tick has executed.
|
|
84
|
+
/// </summary>
|
|
85
|
+
[Min(0.01f)]
|
|
86
|
+
[ProtoMember(3)]
|
|
87
|
+
public float interval = 1f;
|
|
88
|
+
|
|
89
|
+
/// <summary>
|
|
90
|
+
/// Maximum number of ticks to execute. Zero or negative means unlimited ticks.
|
|
91
|
+
/// </summary>
|
|
92
|
+
[Min(0)]
|
|
93
|
+
[ProtoMember(4)]
|
|
94
|
+
public int maxTicks;
|
|
95
|
+
|
|
96
|
+
/// <summary>
|
|
97
|
+
/// Attribute modifications applied each time the tick fires.
|
|
98
|
+
/// </summary>
|
|
99
|
+
[ProtoMember(5)]
|
|
100
|
+
public List<AttributeModification> modifications = new();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tags
|
|
2
|
+
{
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// Runtime tracking for a periodic effect definition.
|
|
7
|
+
/// </summary>
|
|
8
|
+
internal sealed class PeriodicEffectRuntimeState
|
|
9
|
+
{
|
|
10
|
+
internal bool IsComplete => definition.maxTicks > 0 && ExecutedTicks >= definition.maxTicks;
|
|
11
|
+
|
|
12
|
+
internal float NextTickTime { get; private set; }
|
|
13
|
+
|
|
14
|
+
internal int ExecutedTicks { get; private set; }
|
|
15
|
+
|
|
16
|
+
internal readonly PeriodicEffectDefinition definition;
|
|
17
|
+
internal readonly float interval;
|
|
18
|
+
|
|
19
|
+
internal PeriodicEffectRuntimeState(PeriodicEffectDefinition definition, float startTime)
|
|
20
|
+
{
|
|
21
|
+
this.definition = definition;
|
|
22
|
+
ExecutedTicks = 0;
|
|
23
|
+
float clampedInterval = Mathf.Max(0.01f, definition.interval);
|
|
24
|
+
interval = clampedInterval;
|
|
25
|
+
NextTickTime = startTime + Mathf.Max(0f, definition.initialDelay);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
internal bool TryConsumeTick(float currentTime)
|
|
29
|
+
{
|
|
30
|
+
if (currentTime < NextTickTime || IsComplete)
|
|
31
|
+
{
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
++ExecutedTicks;
|
|
36
|
+
NextTickTime = currentTime + interval;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -250,6 +250,304 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
250
250
|
return false;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
/// <summary>
|
|
254
|
+
/// Checks whether all of the specified tags are currently active.
|
|
255
|
+
/// Optimized for different collection types with specialized implementations.
|
|
256
|
+
/// </summary>
|
|
257
|
+
/// <param name="effectTags">The collection of tags to check.</param>
|
|
258
|
+
/// <returns><c>true</c> if all tags are active; otherwise, <c>false</c>. Returns <c>false</c> when <paramref name="effectTags"/> is <c>null</c>.</returns>
|
|
259
|
+
public bool HasAllTags(IEnumerable<string> effectTags)
|
|
260
|
+
{
|
|
261
|
+
if (effectTags == null)
|
|
262
|
+
{
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
switch (effectTags)
|
|
267
|
+
{
|
|
268
|
+
case IReadOnlyList<string> list:
|
|
269
|
+
{
|
|
270
|
+
return HasAllTags(list);
|
|
271
|
+
}
|
|
272
|
+
case HashSet<string> hashSet:
|
|
273
|
+
{
|
|
274
|
+
foreach (string effectTag in hashSet)
|
|
275
|
+
{
|
|
276
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
277
|
+
{
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
281
|
+
{
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
case SortedSet<string> sortedSet:
|
|
289
|
+
{
|
|
290
|
+
foreach (string effectTag in sortedSet)
|
|
291
|
+
{
|
|
292
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
293
|
+
{
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
297
|
+
{
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
case Queue<string> queue:
|
|
305
|
+
{
|
|
306
|
+
foreach (string effectTag in queue)
|
|
307
|
+
{
|
|
308
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
309
|
+
{
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
313
|
+
{
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
case Stack<string> stack:
|
|
321
|
+
{
|
|
322
|
+
foreach (string effectTag in stack)
|
|
323
|
+
{
|
|
324
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
325
|
+
{
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
329
|
+
{
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
case LinkedList<string> linkedList:
|
|
337
|
+
{
|
|
338
|
+
foreach (string effectTag in linkedList)
|
|
339
|
+
{
|
|
340
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
341
|
+
{
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
345
|
+
{
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
foreach (string effectTag in effectTags)
|
|
355
|
+
{
|
|
356
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
357
|
+
{
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
361
|
+
{
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/// <summary>
|
|
370
|
+
/// Checks whether all of the specified tags are active.
|
|
371
|
+
/// Optimized for IReadOnlyList with index-based iteration.
|
|
372
|
+
/// </summary>
|
|
373
|
+
/// <param name="effectTags">The list of tags to check.</param>
|
|
374
|
+
/// <returns><c>true</c> if all of the tags are active, or if the list is empty; otherwise, <c>false</c>.</returns>
|
|
375
|
+
public bool HasAllTags(IReadOnlyList<string> effectTags)
|
|
376
|
+
{
|
|
377
|
+
if (effectTags == null)
|
|
378
|
+
{
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for (int i = 0; i < effectTags.Count; ++i)
|
|
383
|
+
{
|
|
384
|
+
string effectTag = effectTags[i];
|
|
385
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
386
|
+
{
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!_tagCount.ContainsKey(effectTag))
|
|
391
|
+
{
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/// <summary>
|
|
400
|
+
/// Determines whether none of the specified tags are active.
|
|
401
|
+
/// </summary>
|
|
402
|
+
/// <param name="effectTags">The collection of tags to inspect.</param>
|
|
403
|
+
/// <returns>
|
|
404
|
+
/// <c>true</c> when the collection is <c>null</c>, empty, or every tag is currently inactive; otherwise, <c>false</c>.
|
|
405
|
+
/// </returns>
|
|
406
|
+
/// <example>
|
|
407
|
+
/// <code>
|
|
408
|
+
/// if (tagHandler.HasNoneOfTags(new[] { "Stunned", "Frozen" }))
|
|
409
|
+
/// {
|
|
410
|
+
/// EnablePlayerInput();
|
|
411
|
+
/// }
|
|
412
|
+
/// </code>
|
|
413
|
+
/// </example>
|
|
414
|
+
public bool HasNoneOfTags(IEnumerable<string> effectTags)
|
|
415
|
+
{
|
|
416
|
+
if (effectTags == null)
|
|
417
|
+
{
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return !HasAnyTag(effectTags);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/// <summary>
|
|
425
|
+
/// Determines whether none of the specified tags are active.
|
|
426
|
+
/// </summary>
|
|
427
|
+
/// <param name="effectTags">The list of tags to inspect.</param>
|
|
428
|
+
/// <returns>
|
|
429
|
+
/// <c>true</c> when the list is <c>null</c>, empty, or every tag is currently inactive; otherwise, <c>false</c>.
|
|
430
|
+
/// </returns>
|
|
431
|
+
public bool HasNoneOfTags(IReadOnlyList<string> effectTags)
|
|
432
|
+
{
|
|
433
|
+
if (effectTags == null)
|
|
434
|
+
{
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return !HasAnyTag(effectTags);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// <summary>
|
|
442
|
+
/// Attempts to retrieve the active instance count for the specified tag.
|
|
443
|
+
/// </summary>
|
|
444
|
+
/// <param name="effectTag">The tag whose count should be retrieved.</param>
|
|
445
|
+
/// <param name="count">
|
|
446
|
+
/// When this method returns, contains the active count for the tag (cast to <see cref="int"/>) if found; otherwise, zero.
|
|
447
|
+
/// </param>
|
|
448
|
+
/// <returns><c>true</c> if the tag is currently tracked; otherwise, <c>false</c>.</returns>
|
|
449
|
+
/// <example>
|
|
450
|
+
/// <code>
|
|
451
|
+
/// if (tagHandler.TryGetTagCount("Poisoned", out int stacks) && stacks >= 3)
|
|
452
|
+
/// {
|
|
453
|
+
/// TriggerCriticalWarning();
|
|
454
|
+
/// }
|
|
455
|
+
/// </code>
|
|
456
|
+
/// </example>
|
|
457
|
+
public bool TryGetTagCount(string effectTag, out int count)
|
|
458
|
+
{
|
|
459
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
460
|
+
{
|
|
461
|
+
count = default;
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (_tagCount.TryGetValue(effectTag, out uint uintCount))
|
|
466
|
+
{
|
|
467
|
+
count = unchecked((int)uintCount);
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
count = default;
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/// <summary>
|
|
476
|
+
/// Retrieves the set of currently active tags into an optional buffer.
|
|
477
|
+
/// </summary>
|
|
478
|
+
/// <param name="buffer">
|
|
479
|
+
/// Optional list to populate. When <c>null</c>, a new list is created. The buffer is cleared before population.
|
|
480
|
+
/// </param>
|
|
481
|
+
/// <returns>The populated buffer containing all active tags.</returns>
|
|
482
|
+
/// <example>
|
|
483
|
+
/// <code>
|
|
484
|
+
/// List<string> activeTags = tagHandler.GetActiveTags(_reusableTagBuffer);
|
|
485
|
+
/// if (activeTags.Contains("Rooted"))
|
|
486
|
+
/// {
|
|
487
|
+
/// DisableMovement();
|
|
488
|
+
/// }
|
|
489
|
+
/// </code>
|
|
490
|
+
/// </example>
|
|
491
|
+
public List<string> GetActiveTags(List<string> buffer = null)
|
|
492
|
+
{
|
|
493
|
+
buffer ??= new List<string>();
|
|
494
|
+
buffer.Clear();
|
|
495
|
+
foreach (KeyValuePair<string, uint> entry in _tagCount)
|
|
496
|
+
{
|
|
497
|
+
if (entry.Value == 0)
|
|
498
|
+
{
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
buffer.Add(entry.Key);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return buffer;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/// <summary>
|
|
509
|
+
/// Collects all active effect handles that currently contribute the specified tag.
|
|
510
|
+
/// </summary>
|
|
511
|
+
/// <param name="effectTag">The tag to query.</param>
|
|
512
|
+
/// <param name="buffer">
|
|
513
|
+
/// Optional list to populate. When <c>null</c>, a new list is created. The buffer is cleared before population.
|
|
514
|
+
/// </param>
|
|
515
|
+
/// <returns>The populated buffer containing matching effect handles.</returns>
|
|
516
|
+
/// <example>
|
|
517
|
+
/// <code>
|
|
518
|
+
/// List<EffectHandle> handles = tagHandler.GetHandlesWithTag("Burning", _handleBuffer);
|
|
519
|
+
/// foreach (EffectHandle handle in handles)
|
|
520
|
+
/// {
|
|
521
|
+
/// effectHandler.RemoveEffect(handle);
|
|
522
|
+
/// }
|
|
523
|
+
/// </code>
|
|
524
|
+
/// </example>
|
|
525
|
+
public List<EffectHandle> GetHandlesWithTag(
|
|
526
|
+
string effectTag,
|
|
527
|
+
List<EffectHandle> buffer = null
|
|
528
|
+
)
|
|
529
|
+
{
|
|
530
|
+
buffer ??= new List<EffectHandle>();
|
|
531
|
+
buffer.Clear();
|
|
532
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
533
|
+
{
|
|
534
|
+
return buffer;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
foreach (EffectHandle handle in _effectHandles.Values)
|
|
538
|
+
{
|
|
539
|
+
if (
|
|
540
|
+
handle.effect.effectTags != null
|
|
541
|
+
&& handle.effect.effectTags.Contains(effectTag)
|
|
542
|
+
)
|
|
543
|
+
{
|
|
544
|
+
buffer.Add(handle);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return buffer;
|
|
549
|
+
}
|
|
550
|
+
|
|
253
551
|
/// <summary>
|
|
254
552
|
/// Applies a tag, incrementing its count. If the tag is new, raises <see cref="OnTagAdded"/>.
|
|
255
553
|
/// Otherwise, raises <see cref="OnTagCountChanged"/>.
|
|
@@ -261,22 +559,52 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
261
559
|
}
|
|
262
560
|
|
|
263
561
|
/// <summary>
|
|
264
|
-
/// Removes
|
|
562
|
+
/// Removes all instances of the specified tag and returns the contributing effect handles.
|
|
265
563
|
/// </summary>
|
|
266
564
|
/// <param name="effectTag">The tag to remove.</param>
|
|
267
|
-
/// <param name="
|
|
268
|
-
///
|
|
269
|
-
///
|
|
565
|
+
/// <param name="buffer">
|
|
566
|
+
/// Optional list that receives the handles whose effects applied <paramref name="effectTag"/>.
|
|
567
|
+
/// When <c>null</c>, a new list is created. The buffer is cleared before population.
|
|
270
568
|
/// </param>
|
|
271
|
-
|
|
569
|
+
/// <returns>
|
|
570
|
+
/// The populated buffer of handles whose tags were removed. The buffer is empty when the tag was not active.
|
|
571
|
+
/// </returns>
|
|
572
|
+
/// <example>
|
|
573
|
+
/// <code>
|
|
574
|
+
/// List<EffectHandle> dispelled = tagHandler.RemoveTag("Stunned", _handles);
|
|
575
|
+
/// foreach (EffectHandle handle in dispelled)
|
|
576
|
+
/// {
|
|
577
|
+
/// NotifyDispel(handle);
|
|
578
|
+
/// }
|
|
579
|
+
/// </code>
|
|
580
|
+
/// </example>
|
|
581
|
+
public List<EffectHandle> RemoveTag(string effectTag, List<EffectHandle> buffer = null)
|
|
272
582
|
{
|
|
273
|
-
|
|
583
|
+
buffer ??= new List<EffectHandle>();
|
|
584
|
+
buffer.Clear();
|
|
585
|
+
if (string.IsNullOrEmpty(effectTag))
|
|
274
586
|
{
|
|
275
|
-
|
|
276
|
-
|
|
587
|
+
return buffer;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
foreach (EffectHandle handle in _effectHandles.Values)
|
|
591
|
+
{
|
|
592
|
+
if (
|
|
593
|
+
handle.effect.effectTags != null
|
|
594
|
+
&& handle.effect.effectTags.Contains(effectTag)
|
|
595
|
+
)
|
|
596
|
+
{
|
|
597
|
+
buffer.Add(handle);
|
|
598
|
+
}
|
|
277
599
|
}
|
|
278
600
|
|
|
279
|
-
|
|
601
|
+
foreach (EffectHandle handle in buffer)
|
|
602
|
+
{
|
|
603
|
+
ForceRemoveTags(handle);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
InternalRemoveTag(effectTag, allInstances: true);
|
|
607
|
+
return buffer;
|
|
280
608
|
}
|
|
281
609
|
|
|
282
610
|
/// <summary>
|
|
@@ -292,7 +620,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
292
620
|
return;
|
|
293
621
|
}
|
|
294
622
|
|
|
295
|
-
|
|
623
|
+
ApplyEffectTags(handle.effect);
|
|
296
624
|
}
|
|
297
625
|
|
|
298
626
|
/// <summary>
|
|
@@ -302,10 +630,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
302
630
|
/// <param name="effect">The effect containing tags to apply.</param>
|
|
303
631
|
public void ForceApplyEffect(AttributeEffect effect)
|
|
304
632
|
{
|
|
305
|
-
|
|
306
|
-
{
|
|
307
|
-
InternalApplyTag(effectTag);
|
|
308
|
-
}
|
|
633
|
+
ApplyEffectTags(effect);
|
|
309
634
|
}
|
|
310
635
|
|
|
311
636
|
/// <summary>
|
|
@@ -321,11 +646,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
321
646
|
return false;
|
|
322
647
|
}
|
|
323
648
|
|
|
324
|
-
|
|
325
|
-
{
|
|
326
|
-
InternalRemoveTag(effectTag);
|
|
327
|
-
}
|
|
328
|
-
|
|
649
|
+
RemoveEffectTags(appliedHandle.effect);
|
|
329
650
|
return true;
|
|
330
651
|
}
|
|
331
652
|
|
|
@@ -346,7 +667,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
346
667
|
}
|
|
347
668
|
}
|
|
348
669
|
|
|
349
|
-
private void InternalRemoveTag(string effectTag)
|
|
670
|
+
private void InternalRemoveTag(string effectTag, bool allInstances)
|
|
350
671
|
{
|
|
351
672
|
if (!_tagCount.TryGetValue(effectTag, out uint count))
|
|
352
673
|
{
|
|
@@ -355,7 +676,14 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
355
676
|
|
|
356
677
|
if (count != 0)
|
|
357
678
|
{
|
|
358
|
-
|
|
679
|
+
if (!allInstances)
|
|
680
|
+
{
|
|
681
|
+
--count;
|
|
682
|
+
}
|
|
683
|
+
else
|
|
684
|
+
{
|
|
685
|
+
count = 0;
|
|
686
|
+
}
|
|
359
687
|
}
|
|
360
688
|
|
|
361
689
|
if (count == 0)
|
|
@@ -369,5 +697,31 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
369
697
|
OnTagCountChanged?.Invoke(effectTag, count);
|
|
370
698
|
}
|
|
371
699
|
}
|
|
700
|
+
|
|
701
|
+
private void ApplyEffectTags(AttributeEffect effect)
|
|
702
|
+
{
|
|
703
|
+
if (effect.effectTags == null)
|
|
704
|
+
{
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
foreach (string effectTag in effect.effectTags)
|
|
709
|
+
{
|
|
710
|
+
InternalApplyTag(effectTag);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private void RemoveEffectTags(AttributeEffect effect)
|
|
715
|
+
{
|
|
716
|
+
if (effect.effectTags == null)
|
|
717
|
+
{
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
foreach (string effectTag in effect.effectTags)
|
|
722
|
+
{
|
|
723
|
+
InternalRemoveTag(effectTag, allInstances: false);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
372
726
|
}
|
|
373
727
|
}
|