com.wallstop-studios.unity-helpers 2.0.0-rc68 → 2.0.0-rc70

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.
Files changed (74) hide show
  1. package/Editor/AnimationCopier.cs +875 -93
  2. package/Editor/AnimationCreator.cs +840 -137
  3. package/Editor/AnimationEventEditor.cs +4 -4
  4. package/Editor/AnimatorControllerCopier.cs +3 -3
  5. package/Editor/Extensions/UnityExtensions.cs +26 -0
  6. package/Editor/Extensions/UnityExtensions.cs.meta +3 -0
  7. package/Editor/Extensions.meta +3 -0
  8. package/Editor/FitTextureSizeWindow.cs +371 -0
  9. package/Editor/PrefabChecker.cs +716 -0
  10. package/Editor/SpriteAtlasGenerator.cs +598 -0
  11. package/Editor/SpriteAtlasGenerator.cs.meta +3 -0
  12. package/Editor/SpriteCropper.cs +407 -0
  13. package/Editor/SpriteCropper.cs.meta +3 -0
  14. package/Editor/SpriteSettingsApplier.cs +756 -92
  15. package/Editor/TextureResizerWizard.cs +3 -3
  16. package/Editor/TextureSettingsApplier.cs +9 -9
  17. package/Editor/WShowIfPropertyDrawer.cs +2 -2
  18. package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs +15 -0
  19. package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs.meta +3 -0
  20. package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs +1 -1
  21. package/Runtime/Core/Extension/EnumExtensions.cs +176 -1
  22. package/Runtime/Core/Extension/UnityExtensions.cs +1 -1
  23. package/Runtime/Core/Helper/Partials/LogHelpers.cs +1 -1
  24. package/Runtime/Core/Helper/Partials/MathHelpers.cs +1 -1
  25. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +3 -4
  26. package/Runtime/Tags/Attribute.cs +205 -0
  27. package/Runtime/Tags/Attribute.cs.meta +3 -0
  28. package/Runtime/Tags/AttributeEffect.cs +276 -0
  29. package/Runtime/Tags/AttributeEffect.cs.meta +3 -0
  30. package/Runtime/Tags/AttributeModification.cs +51 -0
  31. package/Runtime/Tags/AttributeModification.cs.meta +3 -0
  32. package/Runtime/Tags/AttributeUtilities.cs +209 -0
  33. package/Runtime/Tags/AttributeUtilities.cs.meta +3 -0
  34. package/Runtime/Tags/AttributesComponent.cs +163 -0
  35. package/Runtime/Tags/AttributesComponent.cs.meta +3 -0
  36. package/Runtime/Tags/CosmeticEffectComponent.cs +50 -0
  37. package/Runtime/Tags/CosmeticEffectComponent.cs.meta +3 -0
  38. package/Runtime/Tags/CosmeticEffectData.cs +63 -0
  39. package/Runtime/Tags/CosmeticEffectData.cs.meta +3 -0
  40. package/Runtime/Tags/EffectHandle.cs +63 -0
  41. package/Runtime/Tags/EffectHandle.cs.meta +3 -0
  42. package/Runtime/Tags/EffectHandler.cs +380 -0
  43. package/Runtime/Tags/EffectHandler.cs.meta +3 -0
  44. package/Runtime/Tags/ModificationAction.cs +9 -0
  45. package/Runtime/Tags/ModificationAction.cs.meta +3 -0
  46. package/Runtime/Tags/ModifierDurationType.cs +13 -0
  47. package/Runtime/Tags/ModifierDurationType.cs.meta +3 -0
  48. package/Runtime/{Utils → Tags}/TagHandler.cs +42 -5
  49. package/Runtime/Tags.meta +3 -0
  50. package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs +1 -1
  51. package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +1 -1
  52. package/Tests/Runtime/DataStructures/QuadTreeTests.cs +1 -1
  53. package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +1 -1
  54. package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs +1 -1
  55. package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs +1 -1
  56. package/Tests/Runtime/Extensions/EnumExtensionTests.cs +1 -1
  57. package/Tests/Runtime/Extensions/IListExtensionTests.cs +1 -1
  58. package/Tests/Runtime/Extensions/LoggingExtensionTests.cs +1 -1
  59. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +1 -1
  60. package/Tests/Runtime/Extensions/StringExtensionTests.cs +1 -1
  61. package/Tests/Runtime/Helper/ObjectHelperTests.cs +1 -0
  62. package/Tests/Runtime/Helper/WallMathTests.cs +1 -1
  63. package/Tests/Runtime/Performance/KDTreePerformanceTests.cs +1 -1
  64. package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +1 -1
  65. package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +1 -1
  66. package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs +1 -1
  67. package/Tests/Runtime/Random/RandomTestBase.cs +2 -2
  68. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +1 -1
  69. package/package.json +1 -1
  70. package/Editor/FitTextureSizeWizard.cs +0 -147
  71. package/Editor/PrefabCheckWizard.cs +0 -167
  72. /package/Editor/{FitTextureSizeWizard.cs.meta → FitTextureSizeWindow.cs.meta} +0 -0
  73. /package/Editor/{PrefabCheckWizard.cs.meta → PrefabChecker.cs.meta} +0 -0
  74. /package/Runtime/{Utils → Tags}/TagHandler.cs.meta +0 -0
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: e159649b48764561acc18f759a259bc4
3
+ timeCreated: 1746412114
@@ -0,0 +1,50 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System.Collections.Generic;
4
+ using Core.Extension;
5
+ using UnityEngine;
6
+
7
+ [RequireComponent(typeof(CosmeticEffectData))]
8
+ public abstract class CosmeticEffectComponent : MonoBehaviour
9
+ {
10
+ public virtual bool RequiresInstance => false;
11
+
12
+ public virtual bool CleansUpSelf => false;
13
+
14
+ protected readonly List<GameObject> _appliedTargets = new();
15
+
16
+ protected virtual void OnDestroy()
17
+ {
18
+ if (_appliedTargets.Count <= 0)
19
+ {
20
+ return;
21
+ }
22
+
23
+ foreach (GameObject appliedTarget in _appliedTargets.ToArray())
24
+ {
25
+ if (appliedTarget == null)
26
+ {
27
+ continue;
28
+ }
29
+
30
+ OnRemoveEffect(appliedTarget);
31
+ }
32
+ }
33
+
34
+ // Executed when the associated effect is applied.
35
+ public virtual void OnApplyEffect(GameObject target)
36
+ {
37
+ _appliedTargets.Add(target);
38
+ }
39
+
40
+ // Executed when the associated effect is non-instant and removed.
41
+ public virtual void OnRemoveEffect(GameObject target)
42
+ {
43
+ int appliedIndex = _appliedTargets.IndexOf(target);
44
+ if (0 <= appliedIndex)
45
+ {
46
+ _appliedTargets.RemoveAtSwapBack(appliedIndex);
47
+ }
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 5f516b2dba5a4ff89d811a493f54aeb8
3
+ timeCreated: 1746411911
@@ -0,0 +1,63 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Linq;
6
+ using Core.Helper;
7
+ using UnityEngine;
8
+
9
+ [DisallowMultipleComponent]
10
+ public sealed class CosmeticEffectData : MonoBehaviour, IEquatable<CosmeticEffectData>
11
+ {
12
+ // Is an instanced version of this gameObject created when applied.
13
+ public bool RequiresInstancing =>
14
+ _cosmetics.Value.Any(cosmeticEffect => cosmeticEffect.RequiresInstance);
15
+
16
+ [NonSerialized]
17
+ private readonly Lazy<CosmeticEffectComponent[]> _cosmetics;
18
+
19
+ [NonSerialized]
20
+ private readonly Lazy<HashSet<Type>> _cosmeticTypes;
21
+
22
+ public CosmeticEffectData()
23
+ {
24
+ _cosmetics = new Lazy<CosmeticEffectComponent[]>(
25
+ GetComponents<CosmeticEffectComponent>
26
+ );
27
+ _cosmeticTypes = new Lazy<HashSet<Type>>(
28
+ () => _cosmetics.Value.Select(cosmetic => cosmetic.GetType()).ToHashSet()
29
+ );
30
+ }
31
+
32
+ public override bool Equals(object other)
33
+ {
34
+ return other is CosmeticEffectData cosmeticEffectData && Equals(cosmeticEffectData);
35
+ }
36
+
37
+ public bool Equals(CosmeticEffectData other)
38
+ {
39
+ if (ReferenceEquals(this, other))
40
+ {
41
+ return true;
42
+ }
43
+
44
+ if (other == null || GetHashCode() != other.GetHashCode())
45
+ {
46
+ return false;
47
+ }
48
+
49
+ bool componentTypeEquals = _cosmeticTypes.Value.SetEquals(other._cosmeticTypes.Value);
50
+ if (!componentTypeEquals)
51
+ {
52
+ return false;
53
+ }
54
+
55
+ return Helpers.NameEquals(this, other);
56
+ }
57
+
58
+ public override int GetHashCode()
59
+ {
60
+ return _cosmeticTypes.Value.Count.GetHashCode();
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 2488ba4d39ca49d9ab5df6d17db6c1ec
3
+ timeCreated: 1746411879
@@ -0,0 +1,63 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using Core.DataStructure.Adapters;
5
+ using Core.Extension;
6
+
7
+ [Serializable]
8
+ public readonly struct EffectHandle
9
+ : IEquatable<EffectHandle>,
10
+ IComparable<EffectHandle>,
11
+ IComparable
12
+ {
13
+ public readonly AttributeEffect effect;
14
+
15
+ public readonly KGuid id;
16
+
17
+ public static EffectHandle CreateInstance(AttributeEffect effect)
18
+ {
19
+ return new EffectHandle(Guid.NewGuid(), effect);
20
+ }
21
+
22
+ private EffectHandle(KGuid id, AttributeEffect effect)
23
+ {
24
+ this.id = id;
25
+ this.effect = effect;
26
+ }
27
+
28
+ public int CompareTo(EffectHandle other)
29
+ {
30
+ return id.CompareTo(other.id);
31
+ }
32
+
33
+ public int CompareTo(object obj)
34
+ {
35
+ if (obj is EffectHandle other)
36
+ {
37
+ return CompareTo(other);
38
+ }
39
+
40
+ return -1;
41
+ }
42
+
43
+ public override bool Equals(object obj)
44
+ {
45
+ return obj is EffectHandle other && Equals(other);
46
+ }
47
+
48
+ public bool Equals(EffectHandle other)
49
+ {
50
+ return id == other.id;
51
+ }
52
+
53
+ public override int GetHashCode()
54
+ {
55
+ return id.GetHashCode();
56
+ }
57
+
58
+ public override string ToString()
59
+ {
60
+ return this.ToJson();
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 03e83359d25d4e778ea39698c68bceb4
3
+ timeCreated: 1746412025
@@ -0,0 +1,380 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System.Collections.Generic;
4
+ using System.Linq;
5
+ using Core.Attributes;
6
+ using Core.DataStructure.Adapters;
7
+ using Core.Extension;
8
+ using Core.Helper;
9
+ using UnityEngine;
10
+ using Utils;
11
+
12
+ [DisallowMultipleComponent]
13
+ [RequireComponent(typeof(TagHandler))]
14
+ public sealed class EffectHandler : MonoBehaviour
15
+ {
16
+ [SiblingComponent()]
17
+ private TagHandler _tagHandler;
18
+
19
+ [SiblingComponent(optional = true)]
20
+ private AttributesComponent[] _attributes;
21
+
22
+ // Stores instanced cosmetic effect data for associated effects.
23
+ private readonly Dictionary<
24
+ EffectHandle,
25
+ List<CosmeticEffectData>
26
+ > _instancedCosmeticEffects = new();
27
+
28
+ // Stores expiration time of duration effects (We store by Id because it's much cheaper to iterate Guids than it is EffectHandles
29
+ private readonly Dictionary<KGuid, float> _effectExpirations = new();
30
+ private readonly Dictionary<KGuid, EffectHandle> _effectHandlesById = new();
31
+
32
+ // Used only to save allocations in Update()
33
+ private readonly List<KGuid> _expiredEffectIds = new();
34
+ private readonly List<EffectHandle> _appliedEffects = new();
35
+
36
+ private bool _initialized;
37
+
38
+ private void Awake()
39
+ {
40
+ this.AssignRelationalComponents();
41
+ _initialized = true;
42
+ }
43
+
44
+ public EffectHandle? ApplyEffect(AttributeEffect effect)
45
+ {
46
+ EffectHandle? maybeHandle = null;
47
+
48
+ if (effect.durationType != ModifierDurationType.Instant)
49
+ {
50
+ if (effect.durationType == ModifierDurationType.Duration)
51
+ {
52
+ foreach (EffectHandle appliedEffect in _appliedEffects)
53
+ {
54
+ if (appliedEffect.effect == null)
55
+ {
56
+ continue;
57
+ }
58
+
59
+ string serializableName = appliedEffect.effect.name;
60
+ if (string.Equals(effect.name, serializableName))
61
+ {
62
+ maybeHandle = appliedEffect;
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ maybeHandle ??= EffectHandle.CreateInstance(effect);
69
+ }
70
+
71
+ if (maybeHandle.HasValue)
72
+ {
73
+ EffectHandle handle = maybeHandle.Value;
74
+ InternalApplyEffect(handle);
75
+ if (
76
+ effect.durationType == ModifierDurationType.Duration
77
+ && (effect.resetDurationOnReapplication || !_appliedEffects.Contains(handle))
78
+ )
79
+ {
80
+ KGuid handleId = handle.id;
81
+ _effectExpirations[handleId] = Time.time + effect.duration;
82
+ _effectHandlesById[handleId] = handle;
83
+ }
84
+ }
85
+ else
86
+ {
87
+ InternalApplyEffect(effect);
88
+ }
89
+
90
+ return maybeHandle;
91
+ }
92
+
93
+ public void RemoveEffect(EffectHandle handle)
94
+ {
95
+ InternalRemoveEffect(handle);
96
+ _ = _appliedEffects.Remove(handle);
97
+ }
98
+
99
+ public void RemoveAllEffects()
100
+ {
101
+ foreach (EffectHandle handle in _appliedEffects.ToArray())
102
+ {
103
+ InternalRemoveEffect(handle);
104
+ }
105
+ _appliedEffects.Clear();
106
+ }
107
+
108
+ private void InternalRemoveEffect(EffectHandle handle)
109
+ {
110
+ foreach (AttributesComponent attributesComponent in _attributes)
111
+ {
112
+ attributesComponent.ForceRemoveAttributeModifications(handle);
113
+ }
114
+
115
+ if (!_initialized && _tagHandler == null)
116
+ {
117
+ this.AssignRelationalComponents();
118
+ }
119
+
120
+ // Then, tags are removed (so cosmetic components can look up if any tags are still applied)
121
+ if (_tagHandler != null)
122
+ {
123
+ _ = _tagHandler.ForceRemoveTags(handle);
124
+ }
125
+
126
+ KGuid handleId = handle.id;
127
+ _ = _effectExpirations.Remove(handleId);
128
+ _ = _effectHandlesById.Remove(handleId);
129
+ InternalRemoveCosmeticEffects(handle);
130
+ }
131
+
132
+ private void InternalApplyEffect(EffectHandle handle)
133
+ {
134
+ bool exists = _appliedEffects.Contains(handle);
135
+ if (!exists)
136
+ {
137
+ _appliedEffects.Add(handle);
138
+ }
139
+
140
+ AttributeEffect effect = handle.effect;
141
+ if (effect.durationType == ModifierDurationType.Duration)
142
+ {
143
+ if (effect.resetDurationOnReapplication || !exists)
144
+ {
145
+ KGuid handleId = handle.id;
146
+ _effectExpirations[handleId] = Time.time + effect.duration;
147
+ _effectHandlesById[handleId] = handle;
148
+ }
149
+ }
150
+
151
+ if (!_initialized && _tagHandler == null)
152
+ {
153
+ this.AssignRelationalComponents();
154
+ }
155
+
156
+ if (_tagHandler != null && effect.effectTags is { Count: > 0 })
157
+ {
158
+ _tagHandler.ForceApplyTags(handle);
159
+ }
160
+
161
+ if (effect.cosmeticEffects is { Count: > 0 })
162
+ {
163
+ InternalApplyCosmeticEffects(handle);
164
+ }
165
+
166
+ if (effect.modifications is { Count: > 0 })
167
+ {
168
+ foreach (AttributesComponent attributesComponent in _attributes)
169
+ {
170
+ attributesComponent.ForceApplyAttributeModifications(handle);
171
+ }
172
+ }
173
+ }
174
+
175
+ private void InternalApplyEffect(AttributeEffect effect)
176
+ {
177
+ if (!_initialized && _tagHandler == null)
178
+ {
179
+ this.AssignRelationalComponents();
180
+ }
181
+
182
+ if (_tagHandler != null && effect.effectTags is { Count: > 0 })
183
+ {
184
+ _tagHandler.ForceApplyEffect(effect);
185
+ }
186
+
187
+ if (effect.cosmeticEffects is { Count: > 0 })
188
+ {
189
+ InternalApplyCosmeticEffects(effect);
190
+ }
191
+
192
+ if (effect.modifications is { Count: > 0 })
193
+ {
194
+ foreach (AttributesComponent attributesComponent in _attributes)
195
+ {
196
+ attributesComponent.ForceApplyAttributeModifications(effect);
197
+ }
198
+ }
199
+ }
200
+
201
+ private void InternalApplyCosmeticEffects(EffectHandle handle)
202
+ {
203
+ if (_instancedCosmeticEffects.ContainsKey(handle))
204
+ {
205
+ return;
206
+ }
207
+
208
+ List<CosmeticEffectData> instancedCosmeticData = null;
209
+
210
+ AttributeEffect effect = handle.effect;
211
+
212
+ foreach (CosmeticEffectData cosmeticEffectData in effect.cosmeticEffects)
213
+ {
214
+ CosmeticEffectData cosmeticEffect = cosmeticEffectData;
215
+ if (cosmeticEffect == null)
216
+ {
217
+ this.LogError(
218
+ $"CosmeticEffectData is null for effect {effect:json}, cannot determine instancing scheme."
219
+ );
220
+ continue;
221
+ }
222
+
223
+ if (cosmeticEffectData.RequiresInstancing)
224
+ {
225
+ cosmeticEffect = Instantiate(
226
+ cosmeticEffectData,
227
+ transform.position,
228
+ Quaternion.identity
229
+ );
230
+ cosmeticEffect.transform.SetParent(transform, true);
231
+ (instancedCosmeticData ??= new List<CosmeticEffectData>()).Add(cosmeticEffect);
232
+ }
233
+
234
+ Buffers<CosmeticEffectComponent>.List.Clear();
235
+ cosmeticEffect.GetComponents(Buffers<CosmeticEffectComponent>.List);
236
+ foreach (
237
+ CosmeticEffectComponent cosmeticComponent in Buffers<CosmeticEffectComponent>.List
238
+ )
239
+ {
240
+ cosmeticComponent.OnApplyEffect(gameObject);
241
+ }
242
+ }
243
+
244
+ if (instancedCosmeticData != null)
245
+ {
246
+ _instancedCosmeticEffects.Add(handle, instancedCosmeticData);
247
+ }
248
+ }
249
+
250
+ private void InternalApplyCosmeticEffects(AttributeEffect attributeEffect)
251
+ {
252
+ foreach (CosmeticEffectData cosmeticEffectData in attributeEffect.cosmeticEffects)
253
+ {
254
+ CosmeticEffectData cosmeticEffect = cosmeticEffectData;
255
+ if (cosmeticEffect == null)
256
+ {
257
+ this.LogError(
258
+ $"CosmeticEffectData is null for effect {attributeEffect:json}, cannot determine instancing scheme."
259
+ );
260
+ continue;
261
+ }
262
+
263
+ if (cosmeticEffectData.RequiresInstancing)
264
+ {
265
+ this.LogError(
266
+ $"CosmeticEffectData requires instancing, but can't instance (no handle)."
267
+ );
268
+ continue;
269
+ }
270
+
271
+ Buffers<CosmeticEffectComponent>.List.Clear();
272
+ cosmeticEffect.GetComponents(Buffers<CosmeticEffectComponent>.List);
273
+ foreach (
274
+ CosmeticEffectComponent cosmeticComponent in Buffers<CosmeticEffectComponent>.List
275
+ )
276
+ {
277
+ cosmeticComponent.OnApplyEffect(gameObject);
278
+ }
279
+ }
280
+ }
281
+
282
+ private void InternalRemoveCosmeticEffects(EffectHandle handle)
283
+ {
284
+ if (
285
+ !_instancedCosmeticEffects.TryGetValue(
286
+ handle,
287
+ out List<CosmeticEffectData> cosmeticDatas
288
+ )
289
+ )
290
+ {
291
+ // If we don't have instanced cosmetic effects, then they were applied directly to the cosmetic data
292
+ foreach (
293
+ CosmeticEffectData cosmeticEffectData in handle.effect.cosmeticEffects
294
+ ?? Enumerable.Empty<CosmeticEffectData>()
295
+ )
296
+ {
297
+ if (cosmeticEffectData.RequiresInstancing)
298
+ {
299
+ this.LogWarn(
300
+ $"Double-deregistration detected for handle {handle:json}. Existing handles: [{(string.Join(",", _instancedCosmeticEffects.Keys))}]."
301
+ );
302
+ continue;
303
+ }
304
+
305
+ Buffers<CosmeticEffectComponent>.List.Clear();
306
+ cosmeticEffectData.GetComponents(Buffers<CosmeticEffectComponent>.List);
307
+ foreach (
308
+ CosmeticEffectComponent cosmeticComponent in Buffers<CosmeticEffectComponent>.List
309
+ )
310
+ {
311
+ cosmeticComponent.OnRemoveEffect(gameObject);
312
+ }
313
+ }
314
+
315
+ return;
316
+ }
317
+
318
+ foreach (
319
+ CosmeticEffectComponent cosmeticComponent in cosmeticDatas.SelectMany(
320
+ cosmeticData => cosmeticData.GetComponents<CosmeticEffectComponent>()
321
+ )
322
+ )
323
+ {
324
+ cosmeticComponent.OnRemoveEffect(gameObject);
325
+ }
326
+
327
+ foreach (CosmeticEffectData data in cosmeticDatas)
328
+ {
329
+ bool shouldDestroyGameObject = true;
330
+ Buffers<CosmeticEffectComponent>.List.Clear();
331
+ data.GetComponents(Buffers<CosmeticEffectComponent>.List);
332
+ foreach (
333
+ CosmeticEffectComponent cosmeticEffect in Buffers<CosmeticEffectComponent>.List
334
+ )
335
+ {
336
+ if (cosmeticEffect.CleansUpSelf)
337
+ {
338
+ shouldDestroyGameObject = false;
339
+ continue;
340
+ }
341
+
342
+ cosmeticEffect.Destroy();
343
+ }
344
+
345
+ if (shouldDestroyGameObject)
346
+ {
347
+ data.gameObject.Destroy();
348
+ }
349
+ }
350
+
351
+ _ = _instancedCosmeticEffects.Remove(handle);
352
+ }
353
+
354
+ private void Update()
355
+ {
356
+ if (_effectExpirations.Count <= 0)
357
+ {
358
+ return;
359
+ }
360
+
361
+ _expiredEffectIds.Clear();
362
+ float currentTime = Time.time;
363
+ foreach (KeyValuePair<KGuid, float> entry in _effectExpirations)
364
+ {
365
+ if (entry.Value < currentTime)
366
+ {
367
+ _expiredEffectIds.Add(entry.Key);
368
+ }
369
+ }
370
+
371
+ foreach (KGuid expiredHandleId in _expiredEffectIds)
372
+ {
373
+ if (_effectHandlesById.TryGetValue(expiredHandleId, out EffectHandle expiredHandle))
374
+ {
375
+ RemoveEffect(expiredHandle);
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 71af441977c84ab89137ea71d50f9711
3
+ timeCreated: 1746412073
@@ -0,0 +1,9 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ public enum ModificationAction
4
+ {
5
+ Addition = 0,
6
+ Multiplication = 1,
7
+ Override = 2,
8
+ }
9
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 9bb8be0f5a12424ebeb7a85407acb829
3
+ timeCreated: 1746412003
@@ -0,0 +1,13 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+
5
+ public enum ModifierDurationType
6
+ {
7
+ [Obsolete("Please use a valid value.")]
8
+ None = 0,
9
+ Instant = 1,
10
+ Duration = 2,
11
+ Infinite = 3,
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: a9634fe2d4e84180a4743b759e45ae07
3
+ timeCreated: 1746411840
@@ -1,21 +1,23 @@
1
- namespace WallstopStudios.UnityHelpers.Utils
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
2
  {
3
3
  using System;
4
4
  using System.Collections.Generic;
5
+ using Core.DataStructure.Adapters;
5
6
  using Core.Extension;
6
7
  using UnityEngine;
7
8
 
8
9
  [DisallowMultipleComponent]
9
- public class TagHandler : MonoBehaviour
10
+ public sealed class TagHandler : MonoBehaviour
10
11
  {
11
12
  public IReadOnlyCollection<string> Tags => _tagCount.Keys;
12
13
 
13
14
  [SerializeField]
14
- protected List<string> _initialEffectTags = new();
15
+ private List<string> _initialEffectTags = new();
15
16
 
16
- protected readonly Dictionary<string, uint> _tagCount = new(StringComparer.Ordinal);
17
+ private readonly Dictionary<string, uint> _tagCount = new(StringComparer.Ordinal);
18
+ private readonly Dictionary<KGuid, EffectHandle> _effectHandles = new();
17
19
 
18
- protected virtual void Awake()
20
+ private void Awake()
19
21
  {
20
22
  if (_initialEffectTags is { Count: > 0 })
21
23
  {
@@ -74,6 +76,41 @@
74
76
  InternalRemoveTag(effectTag);
75
77
  }
76
78
 
79
+ public void ForceApplyTags(EffectHandle handle)
80
+ {
81
+ KGuid id = handle.id;
82
+ if (!_effectHandles.TryAdd(id, handle))
83
+ {
84
+ return;
85
+ }
86
+
87
+ ForceApplyEffect(handle.effect);
88
+ }
89
+
90
+ public void ForceApplyEffect(AttributeEffect effect)
91
+ {
92
+ foreach (string effectTag in effect.effectTags)
93
+ {
94
+ InternalApplyTag(effectTag);
95
+ }
96
+ }
97
+
98
+ public bool ForceRemoveTags(EffectHandle handle)
99
+ {
100
+ KGuid id = handle.id;
101
+ if (!_effectHandles.Remove(id, out EffectHandle appliedHandle))
102
+ {
103
+ return false;
104
+ }
105
+
106
+ foreach (string effectTag in appliedHandle.effect.effectTags)
107
+ {
108
+ InternalRemoveTag(effectTag);
109
+ }
110
+
111
+ return true;
112
+ }
113
+
77
114
  private void InternalApplyTag(string effectTag)
78
115
  {
79
116
  _ = _tagCount.AddOrUpdate(effectTag, _ => 1U, (_, existing) => existing + 1);
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 2f6f239e36294f98b1e6dcc2d0a1c45c
3
+ timeCreated: 1746411527