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,276 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.ComponentModel;
6
+ using System.Linq;
7
+ using System.Text;
8
+ using Core.Extension;
9
+ using Core.Helper;
10
+ using Newtonsoft.Json;
11
+ using Sirenix.OdinInspector;
12
+
13
+ [Serializable]
14
+ public sealed class AttributeEffect :
15
+ #if ODIN_INSPECTOR
16
+ SerializedScriptableObject
17
+ #else
18
+ ScriptableObject
19
+ #endif
20
+ , IEquatable<AttributeEffect>
21
+ {
22
+ public string HumanReadableDescription => BuildDescription();
23
+
24
+ public readonly List<AttributeModification> modifications = new();
25
+
26
+ public ModifierDurationType durationType = ModifierDurationType.Duration;
27
+
28
+ #if ODIN_INSPECTOR
29
+ [ShowIf("@durationType == ModifierDurationType.Duration")]
30
+ #endif
31
+ public float duration;
32
+
33
+ #if ODIN_INSPECTOR
34
+ [ShowIf("@durationType == ModifierDurationType.Duration")]
35
+ #endif
36
+ public bool resetDurationOnReapplication;
37
+
38
+ public List<string> effectTags = new();
39
+
40
+ [JsonIgnore]
41
+ public readonly List<CosmeticEffectData> cosmeticEffects = new();
42
+
43
+ private List<string> CosmeticEffectsForJson =>
44
+ cosmeticEffects
45
+ ?.Select(cosmeticEffectData => cosmeticEffectData.name)
46
+ .ToList(cosmeticEffects.Count) ?? new List<string>(0);
47
+
48
+ public override string ToString()
49
+ {
50
+ return new
51
+ {
52
+ Description = HumanReadableDescription,
53
+ CosmeticEffects = CosmeticEffectsForJson,
54
+ modifications,
55
+ durationType,
56
+ duration,
57
+ tags = effectTags,
58
+ }.ToJson();
59
+ }
60
+
61
+ private string BuildDescription()
62
+ {
63
+ if (modifications == null)
64
+ {
65
+ return nameof(AttributeEffect);
66
+ }
67
+
68
+ StringBuilder descriptionBuilder = new();
69
+ for (int i = 0; i < modifications.Count; ++i)
70
+ {
71
+ AttributeModification modification = modifications[i];
72
+ switch (modification.action)
73
+ {
74
+ case ModificationAction.Addition:
75
+ {
76
+ if (modification.value < 0)
77
+ {
78
+ _ = descriptionBuilder.Append(modification.value);
79
+ _ = descriptionBuilder.Append(' ');
80
+ }
81
+ else if (modification.value == 0)
82
+ {
83
+ continue;
84
+ }
85
+ else
86
+ {
87
+ _ = descriptionBuilder.AppendFormat("+{0} ", modification.value);
88
+ }
89
+
90
+ break;
91
+ }
92
+ case ModificationAction.Multiplication:
93
+ {
94
+ if (modification.value < 1)
95
+ {
96
+ _ = descriptionBuilder.AppendFormat(
97
+ "-{0}% ",
98
+ (1 - modification.value) * 100
99
+ );
100
+ }
101
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
102
+ else if (modification.value == 1)
103
+ {
104
+ continue;
105
+ }
106
+ else
107
+ {
108
+ _ = descriptionBuilder.AppendFormat(
109
+ "+{0}% ",
110
+ (modification.value - 1) * 100
111
+ );
112
+ }
113
+
114
+ break;
115
+ }
116
+ case ModificationAction.Override:
117
+ {
118
+ _ = descriptionBuilder.AppendFormat("{0} ", modification.value);
119
+ break;
120
+ }
121
+ default:
122
+ {
123
+ throw new InvalidEnumArgumentException(
124
+ nameof(modification.value),
125
+ (int)modification.value,
126
+ typeof(ModificationAction)
127
+ );
128
+ }
129
+ }
130
+
131
+ _ = descriptionBuilder.Append(modification.attribute.ToPascalCase(" "));
132
+ if (i < modifications.Count - 1)
133
+ {
134
+ _ = descriptionBuilder.Append(", ");
135
+ }
136
+ }
137
+
138
+ return descriptionBuilder.ToString();
139
+ }
140
+
141
+ // Needed now since most things are based on serialized attribute effects and each unserialization will be a new instance
142
+ public bool Equals(AttributeEffect other)
143
+ {
144
+ if (ReferenceEquals(this, other))
145
+ {
146
+ return true;
147
+ }
148
+
149
+ if (other == null)
150
+ {
151
+ return false;
152
+ }
153
+
154
+ if (!string.Equals(name, other.name))
155
+ {
156
+ return false;
157
+ }
158
+
159
+ if (durationType != other.durationType)
160
+ {
161
+ return false;
162
+ }
163
+
164
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
165
+ if (duration != other.duration)
166
+ {
167
+ return false;
168
+ }
169
+
170
+ if (resetDurationOnReapplication != other.resetDurationOnReapplication)
171
+ {
172
+ return false;
173
+ }
174
+
175
+ if (modifications == null)
176
+ {
177
+ if (other.modifications != null)
178
+ {
179
+ return false;
180
+ }
181
+ }
182
+ else if (other.modifications == null)
183
+ {
184
+ return false;
185
+ }
186
+ else
187
+ {
188
+ if (modifications.Count != other.modifications.Count)
189
+ {
190
+ return false;
191
+ }
192
+
193
+ for (int i = 0; i < modifications.Count; ++i)
194
+ {
195
+ if (modifications[i] != other.modifications[i])
196
+ {
197
+ return false;
198
+ }
199
+ }
200
+ }
201
+
202
+ if (effectTags == null)
203
+ {
204
+ if (other.effectTags != null)
205
+ {
206
+ return false;
207
+ }
208
+ }
209
+ else if (other.effectTags == null)
210
+ {
211
+ return false;
212
+ }
213
+ else
214
+ {
215
+ if (effectTags.Count != other.effectTags.Count)
216
+ {
217
+ return false;
218
+ }
219
+
220
+ for (int i = 0; i < effectTags.Count; ++i)
221
+ {
222
+ if (effectTags[i] != other.effectTags[i])
223
+ {
224
+ return false;
225
+ }
226
+ }
227
+ }
228
+
229
+ if (cosmeticEffects == null)
230
+ {
231
+ if (other.cosmeticEffects != null)
232
+ {
233
+ return false;
234
+ }
235
+ }
236
+ else if (other.cosmeticEffects == null)
237
+ {
238
+ return false;
239
+ }
240
+ else
241
+ {
242
+ if (cosmeticEffects.Count != other.cosmeticEffects.Count)
243
+ {
244
+ return false;
245
+ }
246
+
247
+ for (int i = 0; i < cosmeticEffects.Count; ++i)
248
+ {
249
+ if (!Equals(cosmeticEffects[i], other.cosmeticEffects[i]))
250
+ {
251
+ return false;
252
+ }
253
+ }
254
+ }
255
+
256
+ return true;
257
+ }
258
+
259
+ public override bool Equals(object obj)
260
+ {
261
+ return ReferenceEquals(this, obj) || obj is AttributeEffect other && Equals(other);
262
+ }
263
+
264
+ public override int GetHashCode()
265
+ {
266
+ return Objects.HashCode(
267
+ modifications?.Count,
268
+ durationType,
269
+ duration,
270
+ resetDurationOnReapplication,
271
+ effectTags?.Count,
272
+ cosmeticEffects?.Count
273
+ );
274
+ }
275
+ }
276
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 000e706d0ccd45d8a776c1a9b9d4928b
3
+ timeCreated: 1746411716
@@ -0,0 +1,51 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using Core.Extension;
5
+ using Core.Helper;
6
+
7
+ [Serializable]
8
+ public struct AttributeModification : IEquatable<AttributeModification>
9
+ {
10
+ [StringInList(typeof(AttributeUtilities), nameof(AttributeUtilities.GetAllAttributeNames))]
11
+ public string attribute;
12
+
13
+ public ModificationAction action;
14
+ public float value;
15
+
16
+ public override string ToString()
17
+ {
18
+ return this.ToJson();
19
+ }
20
+
21
+ public static bool operator !=(AttributeModification lhs, AttributeModification rhs)
22
+ {
23
+ return !(lhs == rhs);
24
+ }
25
+
26
+ public static bool operator ==(AttributeModification lhs, AttributeModification rhs)
27
+ {
28
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
29
+ return string.Equals(lhs.attribute, rhs.attribute)
30
+ && lhs.action == rhs.action
31
+ && lhs.value == rhs.value;
32
+ }
33
+
34
+ public override bool Equals(object obj)
35
+ {
36
+ return obj is AttributeModification other && Equals(other);
37
+ }
38
+
39
+ public override int GetHashCode()
40
+ {
41
+ return Objects.HashCode(attribute, action, value);
42
+ }
43
+
44
+ public bool Equals(AttributeModification other)
45
+ {
46
+ return string.Equals(attribute, other.attribute, StringComparison.Ordinal)
47
+ && action == other.action
48
+ && value.Equals(other.value);
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: aa8638bb1ef14228b7496f0d7b12262d
3
+ timeCreated: 1746411774
@@ -0,0 +1,209 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Linq;
6
+ using System.Reflection;
7
+ using Core.Extension;
8
+ using Core.Helper;
9
+ using Object = UnityEngine.Object;
10
+
11
+ public static class AttributeUtilities
12
+ {
13
+ private static string[] AllAttributeNames;
14
+ private static readonly Dictionary<Type, Dictionary<string, FieldInfo>> AttributeFields =
15
+ new();
16
+
17
+ // TODO: Use TypeCache + serialize
18
+ public static string[] GetAllAttributeNames()
19
+ {
20
+ return AllAttributeNames ??= AppDomain
21
+ .CurrentDomain.GetAssemblies()
22
+ .SelectMany(assembly => assembly.GetTypes())
23
+ .Where(type => !type.IsAbstract)
24
+ .Where(type => type.IsSubclassOf(typeof(AttributesComponent)))
25
+ .SelectMany(type =>
26
+ type.GetFields(
27
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
28
+ )
29
+ )
30
+ .Where(fieldInfo => fieldInfo.FieldType == typeof(Attribute))
31
+ .Select(fieldInfo => fieldInfo.Name)
32
+ .Distinct()
33
+ .Ordered()
34
+ .ToArray();
35
+ }
36
+
37
+ public static bool HasTag(this Object target, string effectTag)
38
+ {
39
+ if (target == null)
40
+ {
41
+ return false;
42
+ }
43
+
44
+ return target.TryGetComponent(out TagHandler tagHandler)
45
+ && tagHandler.HasTag(effectTag);
46
+ }
47
+
48
+ public static bool HasAnyTag(this Object target, IEnumerable<string> effectTags)
49
+ {
50
+ if (target == null)
51
+ {
52
+ return false;
53
+ }
54
+
55
+ return target.TryGetComponent(out TagHandler tagHandler)
56
+ && tagHandler.HasAnyTag(effectTags);
57
+ }
58
+
59
+ public static bool HasAnyTag(this Object target, IReadOnlyList<string> effectTags)
60
+ {
61
+ if (target == null)
62
+ {
63
+ return false;
64
+ }
65
+
66
+ return target.TryGetComponent(out TagHandler tagHandler)
67
+ && tagHandler.HasAnyTag(effectTags);
68
+ }
69
+
70
+ public static EffectHandle? ApplyEffect(this Object target, AttributeEffect attributeEffect)
71
+ {
72
+ if (target == null)
73
+ {
74
+ return null;
75
+ }
76
+
77
+ EffectHandler effectHandler = target.GetGameObject().GetOrAddComponent<EffectHandler>();
78
+ return effectHandler.ApplyEffect(attributeEffect);
79
+ }
80
+
81
+ public static void ApplyEffectsNoAlloc(
82
+ this Object target,
83
+ List<AttributeEffect> attributeEffects
84
+ )
85
+ {
86
+ if (attributeEffects is not { Count: > 0 })
87
+ {
88
+ return;
89
+ }
90
+
91
+ if (target == null)
92
+ {
93
+ return;
94
+ }
95
+ EffectHandler effectHandler = target.GetGameObject().GetOrAddComponent<EffectHandler>();
96
+ foreach (AttributeEffect attributeEffect in attributeEffects)
97
+ {
98
+ _ = effectHandler.ApplyEffect(attributeEffect);
99
+ }
100
+ }
101
+
102
+ public static void ApplyEffectsNoAlloc(
103
+ this Object target,
104
+ IEnumerable<AttributeEffect> attributeEffects
105
+ )
106
+ {
107
+ if (target == null)
108
+ {
109
+ return;
110
+ }
111
+
112
+ EffectHandler effectHandler = target.GetGameObject().GetOrAddComponent<EffectHandler>();
113
+ foreach (AttributeEffect attributeEffect in attributeEffects)
114
+ {
115
+ _ = effectHandler.ApplyEffect(attributeEffect);
116
+ }
117
+ }
118
+
119
+ public static void ApplyEffectsNoAlloc(
120
+ this Object target,
121
+ List<AttributeEffect> attributeEffects,
122
+ List<EffectHandle> effectHandles
123
+ )
124
+ {
125
+ if (target == null)
126
+ {
127
+ return;
128
+ }
129
+
130
+ EffectHandler effectHandler = target.GetGameObject().GetOrAddComponent<EffectHandler>();
131
+ foreach (AttributeEffect attributeEffect in attributeEffects)
132
+ {
133
+ EffectHandle? handle = effectHandler.ApplyEffect(attributeEffect);
134
+ if (handle.HasValue)
135
+ {
136
+ effectHandles.Add(handle.Value);
137
+ }
138
+ }
139
+ }
140
+
141
+ public static List<EffectHandle> ApplyEffects(
142
+ this Object target,
143
+ List<AttributeEffect> attributeEffects
144
+ )
145
+ {
146
+ List<EffectHandle> handles = new(attributeEffects.Count);
147
+ ApplyEffectsNoAlloc(target, attributeEffects, handles);
148
+ return handles;
149
+ }
150
+
151
+ public static void RemoveEffect(this Object target, EffectHandle effectHandle)
152
+ {
153
+ if (target == null)
154
+ {
155
+ return;
156
+ }
157
+
158
+ if (target.TryGetComponent(out EffectHandler effectHandler))
159
+ {
160
+ effectHandler.RemoveEffect(effectHandle);
161
+ }
162
+ }
163
+
164
+ public static void RemoveEffects(this Object target, List<EffectHandle> effectHandles)
165
+ {
166
+ if (target == null || effectHandles.Count <= 0)
167
+ {
168
+ return;
169
+ }
170
+
171
+ if (target.TryGetComponent(out EffectHandler effectHandler))
172
+ {
173
+ foreach (EffectHandle effectHandle in effectHandles)
174
+ {
175
+ effectHandler.RemoveEffect(effectHandle);
176
+ }
177
+ }
178
+ }
179
+
180
+ public static void RemoveAllEffects(this Object target)
181
+ {
182
+ if (target == null)
183
+ {
184
+ return;
185
+ }
186
+
187
+ if (target.TryGetComponent(out EffectHandler effectHandler))
188
+ {
189
+ effectHandler.RemoveAllEffects();
190
+ }
191
+ }
192
+
193
+ public static Dictionary<string, FieldInfo> GetAttributeFields(Type type)
194
+ {
195
+ return AttributeFields.GetOrAdd(
196
+ type,
197
+ inputType =>
198
+ {
199
+ return inputType
200
+ .GetFields(
201
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
202
+ )
203
+ .Where(field => field.FieldType == typeof(Attribute))
204
+ .ToDictionary(field => field.Name, StringComparer.Ordinal);
205
+ }
206
+ );
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: dc4f3eedfcfd48ad845ce12e010b8234
3
+ timeCreated: 1746411795
@@ -0,0 +1,163 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Reflection;
6
+ using Core.Attributes;
7
+ using UnityEngine;
8
+
9
+ [RequireComponent(typeof(TagHandler))]
10
+ [RequireComponent(typeof(EffectHandler))]
11
+ public abstract class AttributesComponent : MonoBehaviour
12
+ {
13
+ public event Action<string, float, float> OnAttributeModified;
14
+
15
+ private readonly Dictionary<string, FieldInfo> _attributeFields;
16
+ private readonly HashSet<EffectHandle> _effectHandles;
17
+
18
+ [SiblingComponent]
19
+ protected TagHandler _tagHandler;
20
+
21
+ [SiblingComponent]
22
+ protected EffectHandler _effectHandler;
23
+
24
+ protected AttributesComponent()
25
+ {
26
+ _attributeFields = AttributeUtilities.GetAttributeFields(GetType());
27
+ _effectHandles = new HashSet<EffectHandle>();
28
+ }
29
+
30
+ protected virtual void Awake()
31
+ {
32
+ this.AssignSiblingComponents();
33
+ }
34
+
35
+ public void ApplyAttributeModifications(
36
+ IEnumerable<AttributeModification> attributeModifications,
37
+ EffectHandle? handle
38
+ )
39
+ {
40
+ if (handle.HasValue)
41
+ {
42
+ ForceApplyAttributeModifications(handle.Value);
43
+ return;
44
+ }
45
+
46
+ InternalApplyAttributeModifications(attributeModifications);
47
+ }
48
+
49
+ public void ForceRemoveAttributeModifications(EffectHandle handle)
50
+ {
51
+ InternalRemoveAttributeModifications(handle);
52
+ }
53
+
54
+ private void InternalApplyAttributeModifications(
55
+ IEnumerable<AttributeModification> attributeModifications
56
+ )
57
+ {
58
+ foreach (AttributeModification modification in attributeModifications)
59
+ {
60
+ if (!TryGetAttribute(modification.attribute, out Attribute attribute))
61
+ {
62
+ continue;
63
+ }
64
+
65
+ float oldValue = attribute;
66
+ attribute.ApplyAttributeModification(modification);
67
+ float currentValue = attribute;
68
+
69
+ OnAttributeModified?.Invoke(modification.attribute, oldValue, currentValue);
70
+ }
71
+ }
72
+
73
+ public void ForceApplyAttributeModifications(EffectHandle handle)
74
+ {
75
+ AttributeEffect effect = handle.effect;
76
+ if (effect.modifications is not { Count: > 0 })
77
+ {
78
+ return;
79
+ }
80
+
81
+ bool isNewEffect = false;
82
+ foreach (AttributeModification modification in effect.modifications)
83
+ {
84
+ if (!TryGetAttribute(modification.attribute, out Attribute attribute))
85
+ {
86
+ continue;
87
+ }
88
+
89
+ isNewEffect = isNewEffect || _effectHandles.Add(handle);
90
+ if (isNewEffect)
91
+ {
92
+ float oldValue = attribute;
93
+ attribute.ApplyAttributeModification(modification, handle);
94
+ float currentValue = attribute;
95
+ OnAttributeModified?.Invoke(modification.attribute, oldValue, currentValue);
96
+ }
97
+ }
98
+ }
99
+
100
+ public void ForceApplyAttributeModifications(AttributeEffect effect)
101
+ {
102
+ if (effect.modifications is not { Count: > 0 })
103
+ {
104
+ return;
105
+ }
106
+
107
+ foreach (AttributeModification modification in effect.modifications)
108
+ {
109
+ if (!TryGetAttribute(modification.attribute, out Attribute attribute))
110
+ {
111
+ continue;
112
+ }
113
+
114
+ float oldValue = attribute;
115
+ attribute.ApplyAttributeModification(modification);
116
+ float currentValue = attribute;
117
+ OnAttributeModified?.Invoke(modification.attribute, oldValue, currentValue);
118
+ }
119
+ }
120
+
121
+ private void InternalRemoveAttributeModifications(EffectHandle handle)
122
+ {
123
+ AttributeEffect effect = handle.effect;
124
+ if (effect.modifications is not { Count: > 0 })
125
+ {
126
+ return;
127
+ }
128
+
129
+ foreach (AttributeModification modification in effect.modifications)
130
+ {
131
+ if (!TryGetAttribute(modification.attribute, out Attribute attribute))
132
+ {
133
+ continue;
134
+ }
135
+
136
+ float oldValue = attribute;
137
+ _ = attribute.RemoveAttributeModification(handle);
138
+ float currentValue = attribute;
139
+ _ = _effectHandles.Remove(handle);
140
+ OnAttributeModified?.Invoke(modification.attribute, oldValue, currentValue);
141
+ }
142
+ }
143
+
144
+ private bool TryGetAttribute(string attributeName, out Attribute attribute)
145
+ {
146
+ if (!_attributeFields.TryGetValue(attributeName, out FieldInfo fieldInfo))
147
+ {
148
+ attribute = default;
149
+ return false;
150
+ }
151
+
152
+ object fieldValue = fieldInfo.GetValue(this);
153
+ if (fieldValue is Attribute fieldAttribute)
154
+ {
155
+ attribute = fieldAttribute;
156
+ return true;
157
+ }
158
+
159
+ attribute = default;
160
+ return false;
161
+ }
162
+ }
163
+ }