com.wallstop-studios.unity-helpers 2.0.0-rc67 → 2.0.0-rc69

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 (33) hide show
  1. package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs +1 -1
  2. package/Runtime/Core/Helper/Helpers.cs +1 -1
  3. package/Runtime/Core/Helper/Partials/LogHelpers.cs +1 -1
  4. package/Runtime/Core/Helper/Partials/MathHelpers.cs +1 -1
  5. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +3 -4
  6. package/Runtime/Tags/Attribute.cs +205 -0
  7. package/Runtime/Tags/Attribute.cs.meta +3 -0
  8. package/Runtime/Tags/AttributeEffect.cs +276 -0
  9. package/Runtime/Tags/AttributeEffect.cs.meta +3 -0
  10. package/Runtime/Tags/AttributeModification.cs +51 -0
  11. package/Runtime/Tags/AttributeModification.cs.meta +3 -0
  12. package/Runtime/Tags/AttributeUtilities.cs +208 -0
  13. package/Runtime/Tags/AttributeUtilities.cs.meta +3 -0
  14. package/Runtime/Tags/AttributesComponent.cs +163 -0
  15. package/Runtime/Tags/AttributesComponent.cs.meta +3 -0
  16. package/Runtime/Tags/CosmeticEffectComponent.cs +50 -0
  17. package/Runtime/Tags/CosmeticEffectComponent.cs.meta +3 -0
  18. package/Runtime/Tags/CosmeticEffectData.cs +63 -0
  19. package/Runtime/Tags/CosmeticEffectData.cs.meta +3 -0
  20. package/Runtime/Tags/EffectHandle.cs +63 -0
  21. package/Runtime/Tags/EffectHandle.cs.meta +3 -0
  22. package/Runtime/Tags/EffectHandler.cs +379 -0
  23. package/Runtime/Tags/EffectHandler.cs.meta +3 -0
  24. package/Runtime/Tags/ModificationAction.cs +9 -0
  25. package/Runtime/Tags/ModificationAction.cs.meta +3 -0
  26. package/Runtime/Tags/ModifierDurationType.cs +13 -0
  27. package/Runtime/Tags/ModifierDurationType.cs.meta +3 -0
  28. package/Runtime/{Utils → Tags}/TagHandler.cs +42 -5
  29. package/Runtime/Tags.meta +3 -0
  30. package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +1 -1
  31. package/Tests/Runtime/Helper/ObjectHelperTests.cs +1 -0
  32. package/package.json +1 -1
  33. /package/Runtime/{Utils → Tags}/TagHandler.cs.meta +0 -0
@@ -0,0 +1,379 @@
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
+ public sealed class EffectHandler : MonoBehaviour
14
+ {
15
+ [SiblingComponent(optional = true)]
16
+ private TagHandler _tagHandler;
17
+
18
+ [SiblingComponent(optional = true)]
19
+ private AttributesComponent[] _attributes;
20
+
21
+ // Stores instanced cosmetic effect data for associated effects.
22
+ private readonly Dictionary<
23
+ EffectHandle,
24
+ List<CosmeticEffectData>
25
+ > _instancedCosmeticEffects = new();
26
+
27
+ // Stores expiration time of duration effects (We store by Id because it's much cheaper to iterate Guids than it is EffectHandles
28
+ private readonly Dictionary<KGuid, float> _effectExpirations = new();
29
+ private readonly Dictionary<KGuid, EffectHandle> _effectHandlesById = new();
30
+
31
+ // Used only to save allocations in Update()
32
+ private readonly List<KGuid> _expiredEffectIds = new();
33
+ private readonly List<EffectHandle> _appliedEffects = new();
34
+
35
+ private bool _initialized;
36
+
37
+ private void Awake()
38
+ {
39
+ this.AssignRelationalComponents();
40
+ _initialized = true;
41
+ }
42
+
43
+ public EffectHandle? ApplyEffect(AttributeEffect effect)
44
+ {
45
+ EffectHandle? maybeHandle = null;
46
+
47
+ if (effect.durationType != ModifierDurationType.Instant)
48
+ {
49
+ if (effect.durationType == ModifierDurationType.Duration)
50
+ {
51
+ foreach (EffectHandle appliedEffect in _appliedEffects)
52
+ {
53
+ if (appliedEffect.effect == null)
54
+ {
55
+ continue;
56
+ }
57
+
58
+ string serializableName = appliedEffect.effect.name;
59
+ if (string.Equals(effect.name, serializableName))
60
+ {
61
+ maybeHandle = appliedEffect;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+
67
+ maybeHandle ??= EffectHandle.CreateInstance(effect);
68
+ }
69
+
70
+ if (maybeHandle.HasValue)
71
+ {
72
+ EffectHandle handle = maybeHandle.Value;
73
+ InternalApplyEffect(handle);
74
+ if (
75
+ effect.durationType == ModifierDurationType.Duration
76
+ && (effect.resetDurationOnReapplication || !_appliedEffects.Contains(handle))
77
+ )
78
+ {
79
+ KGuid handleId = handle.id;
80
+ _effectExpirations[handleId] = Time.time + effect.duration;
81
+ _effectHandlesById[handleId] = handle;
82
+ }
83
+ }
84
+ else
85
+ {
86
+ InternalApplyEffect(effect);
87
+ }
88
+
89
+ return maybeHandle;
90
+ }
91
+
92
+ public void RemoveEffect(EffectHandle handle)
93
+ {
94
+ InternalRemoveEffect(handle);
95
+ _ = _appliedEffects.Remove(handle);
96
+ }
97
+
98
+ public void RemoveAllEffects()
99
+ {
100
+ foreach (EffectHandle handle in _appliedEffects.ToArray())
101
+ {
102
+ InternalRemoveEffect(handle);
103
+ }
104
+ _appliedEffects.Clear();
105
+ }
106
+
107
+ private void InternalRemoveEffect(EffectHandle handle)
108
+ {
109
+ foreach (AttributesComponent attributesComponent in _attributes)
110
+ {
111
+ attributesComponent.ForceRemoveAttributeModifications(handle);
112
+ }
113
+
114
+ if (!_initialized && _tagHandler == null)
115
+ {
116
+ this.AssignRelationalComponents();
117
+ }
118
+
119
+ // Then, tags are removed (so cosmetic components can look up if any tags are still applied)
120
+ if (_tagHandler != null)
121
+ {
122
+ _ = _tagHandler.ForceRemoveTags(handle);
123
+ }
124
+
125
+ KGuid handleId = handle.id;
126
+ _ = _effectExpirations.Remove(handleId);
127
+ _ = _effectHandlesById.Remove(handleId);
128
+ InternalRemoveCosmeticEffects(handle);
129
+ }
130
+
131
+ private void InternalApplyEffect(EffectHandle handle)
132
+ {
133
+ bool exists = _appliedEffects.Contains(handle);
134
+ if (!exists)
135
+ {
136
+ _appliedEffects.Add(handle);
137
+ }
138
+
139
+ AttributeEffect effect = handle.effect;
140
+ if (effect.durationType == ModifierDurationType.Duration)
141
+ {
142
+ if (effect.resetDurationOnReapplication || !exists)
143
+ {
144
+ KGuid handleId = handle.id;
145
+ _effectExpirations[handleId] = Time.time + effect.duration;
146
+ _effectHandlesById[handleId] = handle;
147
+ }
148
+ }
149
+
150
+ if (!_initialized && _tagHandler == null)
151
+ {
152
+ this.AssignRelationalComponents();
153
+ }
154
+
155
+ if (_tagHandler != null && effect.effectTags is { Count: > 0 })
156
+ {
157
+ _tagHandler.ForceApplyTags(handle);
158
+ }
159
+
160
+ if (effect.cosmeticEffects is { Count: > 0 })
161
+ {
162
+ InternalApplyCosmeticEffects(handle);
163
+ }
164
+
165
+ if (effect.modifications is { Count: > 0 })
166
+ {
167
+ foreach (AttributesComponent attributesComponent in _attributes)
168
+ {
169
+ attributesComponent.ForceApplyAttributeModifications(handle);
170
+ }
171
+ }
172
+ }
173
+
174
+ private void InternalApplyEffect(AttributeEffect effect)
175
+ {
176
+ if (!_initialized && _tagHandler == null)
177
+ {
178
+ this.AssignRelationalComponents();
179
+ }
180
+
181
+ if (_tagHandler != null && effect.effectTags is { Count: > 0 })
182
+ {
183
+ _tagHandler.ForceApplyEffect(effect);
184
+ }
185
+
186
+ if (effect.cosmeticEffects is { Count: > 0 })
187
+ {
188
+ InternalApplyCosmeticEffects(effect);
189
+ }
190
+
191
+ if (effect.modifications is { Count: > 0 })
192
+ {
193
+ foreach (AttributesComponent attributesComponent in _attributes)
194
+ {
195
+ attributesComponent.ForceApplyAttributeModifications(effect);
196
+ }
197
+ }
198
+ }
199
+
200
+ private void InternalApplyCosmeticEffects(EffectHandle handle)
201
+ {
202
+ if (_instancedCosmeticEffects.ContainsKey(handle))
203
+ {
204
+ return;
205
+ }
206
+
207
+ List<CosmeticEffectData> instancedCosmeticData = null;
208
+
209
+ AttributeEffect effect = handle.effect;
210
+
211
+ foreach (CosmeticEffectData cosmeticEffectData in effect.cosmeticEffects)
212
+ {
213
+ CosmeticEffectData cosmeticEffect = cosmeticEffectData;
214
+ if (cosmeticEffect == null)
215
+ {
216
+ this.LogError(
217
+ $"CosmeticEffectData is null for effect {effect:json}, cannot determine instancing scheme."
218
+ );
219
+ continue;
220
+ }
221
+
222
+ if (cosmeticEffectData.RequiresInstancing)
223
+ {
224
+ cosmeticEffect = Instantiate(
225
+ cosmeticEffectData,
226
+ transform.position,
227
+ Quaternion.identity
228
+ );
229
+ cosmeticEffect.transform.SetParent(transform, true);
230
+ (instancedCosmeticData ??= new List<CosmeticEffectData>()).Add(cosmeticEffect);
231
+ }
232
+
233
+ Buffers<CosmeticEffectComponent>.List.Clear();
234
+ cosmeticEffect.GetComponents(Buffers<CosmeticEffectComponent>.List);
235
+ foreach (
236
+ CosmeticEffectComponent cosmeticComponent in Buffers<CosmeticEffectComponent>.List
237
+ )
238
+ {
239
+ cosmeticComponent.OnApplyEffect(gameObject);
240
+ }
241
+ }
242
+
243
+ if (instancedCosmeticData != null)
244
+ {
245
+ _instancedCosmeticEffects.Add(handle, instancedCosmeticData);
246
+ }
247
+ }
248
+
249
+ private void InternalApplyCosmeticEffects(AttributeEffect attributeEffect)
250
+ {
251
+ foreach (CosmeticEffectData cosmeticEffectData in attributeEffect.cosmeticEffects)
252
+ {
253
+ CosmeticEffectData cosmeticEffect = cosmeticEffectData;
254
+ if (cosmeticEffect == null)
255
+ {
256
+ this.LogError(
257
+ $"CosmeticEffectData is null for effect {attributeEffect:json}, cannot determine instancing scheme."
258
+ );
259
+ continue;
260
+ }
261
+
262
+ if (cosmeticEffectData.RequiresInstancing)
263
+ {
264
+ this.LogError(
265
+ $"CosmeticEffectData requires instancing, but can't instance (no handle)."
266
+ );
267
+ continue;
268
+ }
269
+
270
+ Buffers<CosmeticEffectComponent>.List.Clear();
271
+ cosmeticEffect.GetComponents(Buffers<CosmeticEffectComponent>.List);
272
+ foreach (
273
+ CosmeticEffectComponent cosmeticComponent in Buffers<CosmeticEffectComponent>.List
274
+ )
275
+ {
276
+ cosmeticComponent.OnApplyEffect(gameObject);
277
+ }
278
+ }
279
+ }
280
+
281
+ private void InternalRemoveCosmeticEffects(EffectHandle handle)
282
+ {
283
+ if (
284
+ !_instancedCosmeticEffects.TryGetValue(
285
+ handle,
286
+ out List<CosmeticEffectData> cosmeticDatas
287
+ )
288
+ )
289
+ {
290
+ // If we don't have instanced cosmetic effects, then they were applied directly to the cosmetic data
291
+ foreach (
292
+ CosmeticEffectData cosmeticEffectData in handle.effect.cosmeticEffects
293
+ ?? Enumerable.Empty<CosmeticEffectData>()
294
+ )
295
+ {
296
+ if (cosmeticEffectData.RequiresInstancing)
297
+ {
298
+ this.LogWarn(
299
+ $"Double-deregistration detected for handle {handle:json}. Existing handles: [{(string.Join(",", _instancedCosmeticEffects.Keys))}]."
300
+ );
301
+ continue;
302
+ }
303
+
304
+ Buffers<CosmeticEffectComponent>.List.Clear();
305
+ cosmeticEffectData.GetComponents(Buffers<CosmeticEffectComponent>.List);
306
+ foreach (
307
+ CosmeticEffectComponent cosmeticComponent in Buffers<CosmeticEffectComponent>.List
308
+ )
309
+ {
310
+ cosmeticComponent.OnRemoveEffect(gameObject);
311
+ }
312
+ }
313
+
314
+ return;
315
+ }
316
+
317
+ foreach (
318
+ CosmeticEffectComponent cosmeticComponent in cosmeticDatas.SelectMany(
319
+ cosmeticData => cosmeticData.GetComponents<CosmeticEffectComponent>()
320
+ )
321
+ )
322
+ {
323
+ cosmeticComponent.OnRemoveEffect(gameObject);
324
+ }
325
+
326
+ foreach (CosmeticEffectData data in cosmeticDatas)
327
+ {
328
+ bool shouldDestroyGameObject = true;
329
+ Buffers<CosmeticEffectComponent>.List.Clear();
330
+ data.GetComponents(Buffers<CosmeticEffectComponent>.List);
331
+ foreach (
332
+ CosmeticEffectComponent cosmeticEffect in Buffers<CosmeticEffectComponent>.List
333
+ )
334
+ {
335
+ if (cosmeticEffect.CleansUpSelf)
336
+ {
337
+ shouldDestroyGameObject = false;
338
+ continue;
339
+ }
340
+
341
+ cosmeticEffect.Destroy();
342
+ }
343
+
344
+ if (shouldDestroyGameObject)
345
+ {
346
+ data.gameObject.Destroy();
347
+ }
348
+ }
349
+
350
+ _ = _instancedCosmeticEffects.Remove(handle);
351
+ }
352
+
353
+ private void Update()
354
+ {
355
+ if (_effectExpirations.Count <= 0)
356
+ {
357
+ return;
358
+ }
359
+
360
+ _expiredEffectIds.Clear();
361
+ float currentTime = Time.time;
362
+ foreach (KeyValuePair<KGuid, float> entry in _effectExpirations)
363
+ {
364
+ if (entry.Value < currentTime)
365
+ {
366
+ _expiredEffectIds.Add(entry.Key);
367
+ }
368
+ }
369
+
370
+ foreach (KGuid expiredHandleId in _expiredEffectIds)
371
+ {
372
+ if (_effectHandlesById.TryGetValue(expiredHandleId, out EffectHandle expiredHandle))
373
+ {
374
+ RemoveEffect(expiredHandle);
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
@@ -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
@@ -2,8 +2,8 @@
2
2
  {
3
3
  using System.Collections.Generic;
4
4
  using System.Linq;
5
+ using Core.DataStructure;
5
6
  using NUnit.Framework;
6
- using WallstopStudios.UnityHelpers.Core.DataStructure;
7
7
  using WallstopStudios.UnityHelpers.Core.Helper;
8
8
  using WallstopStudios.UnityHelpers.Core.Random;
9
9
  using Vector2 = UnityEngine.Vector2;
@@ -2,6 +2,7 @@
2
2
  {
3
3
  using System.Collections;
4
4
  using Core.Helper;
5
+ using Core.Helper.Partials;
5
6
  using JetBrains.Annotations;
6
7
  using NUnit.Framework;
7
8
  using UnityEngine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.0-rc67",
3
+ "version": "2.0.0-rc69",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},
File without changes