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
|
@@ -71,8 +71,6 @@ public class Enemy : MonoBehaviour
|
|
|
71
71
|
2. Add the `RelationalComponentsInstaller` component to the same GameObject
|
|
72
72
|
3. Enable **"Assign Scene On Initialize"** to automatically wire all scene components after the container builds (recommended)
|
|
73
73
|
|
|
74
|
-

|
|
75
|
-
|
|
76
74
|
> 💡 **Beginner tip:** Enable both checkboxes in the inspector:
|
|
77
75
|
>
|
|
78
76
|
> - ✅ **Assign Scene On Initialize** → Auto-wires all scene objects (saves you from calling it manually)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Editor.Attributes
|
|
2
|
+
{
|
|
3
|
+
using System.Reflection;
|
|
4
|
+
using NUnit.Framework;
|
|
5
|
+
using UnityEditor;
|
|
6
|
+
using UnityEngine;
|
|
7
|
+
using WallstopStudios.UnityHelpers.Core.Attributes;
|
|
8
|
+
using WallstopStudios.UnityHelpers.Editor.CustomDrawers;
|
|
9
|
+
using WallstopStudios.UnityHelpers.Tags;
|
|
10
|
+
using WallstopStudios.UnityHelpers.Tests.Editor.Utils;
|
|
11
|
+
|
|
12
|
+
[TestFixture]
|
|
13
|
+
public sealed class WShowIfPropertyDrawerTests : CommonTestBase
|
|
14
|
+
{
|
|
15
|
+
private static readonly FieldInfo AttributeField = typeof(PropertyDrawer).GetField(
|
|
16
|
+
"m_Attribute",
|
|
17
|
+
BindingFlags.Instance | BindingFlags.NonPublic
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
[Test]
|
|
21
|
+
public void BoolConditionHidesFieldWhenFalse()
|
|
22
|
+
{
|
|
23
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
24
|
+
SerializedObject serializedObject = new(container);
|
|
25
|
+
serializedObject.Update();
|
|
26
|
+
|
|
27
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
28
|
+
nameof(TestContainer.boolDependent)
|
|
29
|
+
);
|
|
30
|
+
Assert.NotNull(dependentProperty);
|
|
31
|
+
|
|
32
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
33
|
+
new WShowIfAttribute(nameof(TestContainer.boolCondition))
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
container.boolCondition = false;
|
|
37
|
+
serializedObject.Update();
|
|
38
|
+
float hiddenHeight = drawer.GetPropertyHeight(
|
|
39
|
+
dependentProperty,
|
|
40
|
+
new GUIContent("boolDependent")
|
|
41
|
+
);
|
|
42
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
43
|
+
|
|
44
|
+
container.boolCondition = true;
|
|
45
|
+
serializedObject.Update();
|
|
46
|
+
float shownHeight = drawer.GetPropertyHeight(
|
|
47
|
+
dependentProperty,
|
|
48
|
+
new GUIContent("boolDependent")
|
|
49
|
+
);
|
|
50
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[Test]
|
|
54
|
+
public void EnumConditionMatchesExpectedValue()
|
|
55
|
+
{
|
|
56
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
57
|
+
SerializedObject serializedObject = new(container);
|
|
58
|
+
serializedObject.Update();
|
|
59
|
+
|
|
60
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
61
|
+
nameof(TestContainer.durationDependent)
|
|
62
|
+
);
|
|
63
|
+
Assert.NotNull(dependentProperty);
|
|
64
|
+
|
|
65
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
66
|
+
new WShowIfAttribute(
|
|
67
|
+
nameof(TestContainer.durationType),
|
|
68
|
+
expectedValues: new object[] { ModifierDurationType.Duration }
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
container.durationType = ModifierDurationType.Instant;
|
|
73
|
+
serializedObject.Update();
|
|
74
|
+
float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
75
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
76
|
+
|
|
77
|
+
container.durationType = ModifierDurationType.Duration;
|
|
78
|
+
serializedObject.Update();
|
|
79
|
+
float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
80
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
[Test]
|
|
84
|
+
public void EnumConditionHonorsInverseFlag()
|
|
85
|
+
{
|
|
86
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
87
|
+
SerializedObject serializedObject = new(container);
|
|
88
|
+
serializedObject.Update();
|
|
89
|
+
|
|
90
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
91
|
+
nameof(TestContainer.inverseDependent)
|
|
92
|
+
);
|
|
93
|
+
Assert.NotNull(dependentProperty);
|
|
94
|
+
|
|
95
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
96
|
+
new WShowIfAttribute(
|
|
97
|
+
nameof(TestContainer.durationType),
|
|
98
|
+
inverse: true,
|
|
99
|
+
expectedValues: new object[] { ModifierDurationType.Instant }
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
container.durationType = ModifierDurationType.Instant;
|
|
104
|
+
serializedObject.Update();
|
|
105
|
+
float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
106
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
107
|
+
|
|
108
|
+
container.durationType = ModifierDurationType.Infinite;
|
|
109
|
+
serializedObject.Update();
|
|
110
|
+
float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
111
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[Test]
|
|
115
|
+
public void FloatConditionMatchesExpectedValue()
|
|
116
|
+
{
|
|
117
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
118
|
+
SerializedObject serializedObject = new(container);
|
|
119
|
+
serializedObject.Update();
|
|
120
|
+
|
|
121
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
122
|
+
nameof(TestContainer.floatDependent)
|
|
123
|
+
);
|
|
124
|
+
Assert.NotNull(dependentProperty);
|
|
125
|
+
|
|
126
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
127
|
+
new WShowIfAttribute(
|
|
128
|
+
nameof(TestContainer.floatCondition),
|
|
129
|
+
expectedValues: new object[] { 3.5f }
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
container.floatCondition = 0f;
|
|
134
|
+
serializedObject.Update();
|
|
135
|
+
float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
136
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
137
|
+
|
|
138
|
+
container.floatCondition = 3.5f;
|
|
139
|
+
serializedObject.Update();
|
|
140
|
+
float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
141
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
[Test]
|
|
145
|
+
public void DoubleConditionMatchesEquivalentIntExpectedValue()
|
|
146
|
+
{
|
|
147
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
148
|
+
SerializedObject serializedObject = new(container);
|
|
149
|
+
serializedObject.Update();
|
|
150
|
+
|
|
151
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
152
|
+
nameof(TestContainer.doubleDependent)
|
|
153
|
+
);
|
|
154
|
+
Assert.NotNull(dependentProperty);
|
|
155
|
+
|
|
156
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
157
|
+
new WShowIfAttribute(
|
|
158
|
+
nameof(TestContainer.doubleCondition),
|
|
159
|
+
expectedValues: new object[] { 7 }
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
container.doubleCondition = 2.5d;
|
|
164
|
+
serializedObject.Update();
|
|
165
|
+
float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
166
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
167
|
+
|
|
168
|
+
container.doubleCondition = 7d;
|
|
169
|
+
serializedObject.Update();
|
|
170
|
+
float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
171
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
[Test]
|
|
175
|
+
public void IntConditionMatchesExpectedValue()
|
|
176
|
+
{
|
|
177
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
178
|
+
SerializedObject serializedObject = new(container);
|
|
179
|
+
serializedObject.Update();
|
|
180
|
+
|
|
181
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
182
|
+
nameof(TestContainer.intDependent)
|
|
183
|
+
);
|
|
184
|
+
Assert.NotNull(dependentProperty);
|
|
185
|
+
|
|
186
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
187
|
+
new WShowIfAttribute(
|
|
188
|
+
nameof(TestContainer.intCondition),
|
|
189
|
+
expectedValues: new object[] { 42 }
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
container.intCondition = 7;
|
|
194
|
+
serializedObject.Update();
|
|
195
|
+
float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
196
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
197
|
+
|
|
198
|
+
container.intCondition = 42;
|
|
199
|
+
serializedObject.Update();
|
|
200
|
+
float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
201
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[Test]
|
|
205
|
+
public void StringConditionMatchesExpectedValues()
|
|
206
|
+
{
|
|
207
|
+
TestContainer container = CreateScriptableObject<TestContainer>();
|
|
208
|
+
SerializedObject serializedObject = new(container);
|
|
209
|
+
serializedObject.Update();
|
|
210
|
+
|
|
211
|
+
SerializedProperty dependentProperty = serializedObject.FindProperty(
|
|
212
|
+
nameof(TestContainer.stringDependent)
|
|
213
|
+
);
|
|
214
|
+
Assert.NotNull(dependentProperty);
|
|
215
|
+
|
|
216
|
+
WShowIfPropertyDrawer drawer = CreateDrawer(
|
|
217
|
+
new WShowIfAttribute(
|
|
218
|
+
nameof(TestContainer.stringCondition),
|
|
219
|
+
expectedValues: new object[] { "alpha", "beta" }
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
container.stringCondition = "gamma";
|
|
224
|
+
serializedObject.Update();
|
|
225
|
+
float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
226
|
+
Assert.That(hiddenHeight, Is.Zero);
|
|
227
|
+
|
|
228
|
+
container.stringCondition = "alpha";
|
|
229
|
+
serializedObject.Update();
|
|
230
|
+
float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
|
|
231
|
+
Assert.That(shownHeight, Is.GreaterThan(0f));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private static WShowIfPropertyDrawer CreateDrawer(WShowIfAttribute attribute)
|
|
235
|
+
{
|
|
236
|
+
WShowIfPropertyDrawer drawer = new();
|
|
237
|
+
Assert.NotNull(AttributeField);
|
|
238
|
+
AttributeField.SetValue(drawer, attribute);
|
|
239
|
+
return drawer;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private sealed class TestContainer : ScriptableObject
|
|
243
|
+
{
|
|
244
|
+
public bool boolCondition;
|
|
245
|
+
|
|
246
|
+
[WShowIf(nameof(boolCondition))]
|
|
247
|
+
public int boolDependent;
|
|
248
|
+
|
|
249
|
+
public ModifierDurationType durationType = ModifierDurationType.Instant;
|
|
250
|
+
|
|
251
|
+
[WShowIf(
|
|
252
|
+
nameof(durationType),
|
|
253
|
+
expectedValues: new object[] { ModifierDurationType.Duration }
|
|
254
|
+
)]
|
|
255
|
+
public int durationDependent;
|
|
256
|
+
|
|
257
|
+
[WShowIf(
|
|
258
|
+
nameof(durationType),
|
|
259
|
+
inverse: true,
|
|
260
|
+
expectedValues: new object[] { ModifierDurationType.Instant }
|
|
261
|
+
)]
|
|
262
|
+
public int inverseDependent;
|
|
263
|
+
|
|
264
|
+
public float floatCondition;
|
|
265
|
+
|
|
266
|
+
[WShowIf(nameof(floatCondition), expectedValues: new object[] { 3.5f })]
|
|
267
|
+
public int floatDependent;
|
|
268
|
+
|
|
269
|
+
public double doubleCondition;
|
|
270
|
+
|
|
271
|
+
[WShowIf(nameof(doubleCondition), expectedValues: new object[] { 7 })]
|
|
272
|
+
public int doubleDependent;
|
|
273
|
+
|
|
274
|
+
public int intCondition;
|
|
275
|
+
|
|
276
|
+
[WShowIf(nameof(intCondition), expectedValues: new object[] { 42 })]
|
|
277
|
+
public int intDependent;
|
|
278
|
+
|
|
279
|
+
public string stringCondition;
|
|
280
|
+
|
|
281
|
+
[WShowIf(nameof(stringCondition), expectedValues: new object[] { "alpha", "beta" })]
|
|
282
|
+
public int stringDependent;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -24,7 +24,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Attributes
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
[Test]
|
|
27
|
-
public void
|
|
27
|
+
public void HasRelationalAssignmentsRespectsMetadata()
|
|
28
28
|
{
|
|
29
29
|
AttributeMetadataCache cache = CreateScriptableObject<AttributeMetadataCache>();
|
|
30
30
|
|
|
@@ -62,7 +62,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Attributes
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
[Test]
|
|
65
|
-
public void
|
|
65
|
+
public void AssignIEnumerableAssignsOnlyRelationalTypesAndSkipsNulls()
|
|
66
66
|
{
|
|
67
67
|
AttributeMetadataCache cache = CreateScriptableObject<AttributeMetadataCache>();
|
|
68
68
|
|
|
@@ -34,6 +34,8 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Utils
|
|
|
34
34
|
yield return null;
|
|
35
35
|
DeleteAssetIfExists("Assets/Resources/CustomPath/CustomPathSingleton.asset");
|
|
36
36
|
yield return null;
|
|
37
|
+
DeleteFolderIfEmpty("Assets/Resources/CustomPath");
|
|
38
|
+
yield return null;
|
|
37
39
|
|
|
38
40
|
// For nested test types, Unity cannot create valid .asset files (no script file).
|
|
39
41
|
// Instead, create in-memory instances so the singleton loader can discover them via FindObjectsOfTypeAll.
|
|
@@ -81,6 +83,43 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Utils
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
private static void DeleteFolderIfEmpty(string folderPath)
|
|
87
|
+
{
|
|
88
|
+
if (string.IsNullOrWhiteSpace(folderPath))
|
|
89
|
+
{
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!AssetDatabase.IsValidFolder(folderPath))
|
|
94
|
+
{
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
string[] subFolders = AssetDatabase.GetSubFolders(folderPath);
|
|
99
|
+
if (subFolders != null && subFolders.Length > 0)
|
|
100
|
+
{
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
string[] assetGuids = AssetDatabase.FindAssets(string.Empty, new[] { folderPath });
|
|
105
|
+
for (int i = 0; i < assetGuids.Length; i++)
|
|
106
|
+
{
|
|
107
|
+
string guid = assetGuids[i];
|
|
108
|
+
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
109
|
+
if (
|
|
110
|
+
!string.IsNullOrEmpty(assetPath)
|
|
111
|
+
&& !string.Equals(assetPath, folderPath, System.StringComparison.Ordinal)
|
|
112
|
+
)
|
|
113
|
+
{
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AssetDatabase.DeleteAsset(folderPath);
|
|
119
|
+
AssetDatabase.SaveAssets();
|
|
120
|
+
AssetDatabase.Refresh();
|
|
121
|
+
}
|
|
122
|
+
|
|
84
123
|
private static TType CreateInMemoryInstance<TType>()
|
|
85
124
|
where TType : ScriptableObject
|
|
86
125
|
{
|
|
@@ -144,6 +183,8 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Utils
|
|
|
144
183
|
AssetDatabase.SaveAssets();
|
|
145
184
|
AssetDatabase.Refresh();
|
|
146
185
|
yield return null;
|
|
186
|
+
DeleteFolderIfEmpty("Assets/Resources/CustomPath");
|
|
187
|
+
yield return null;
|
|
147
188
|
// Prefer public API surface over reflection to clean up the cached instance
|
|
148
189
|
if (TestSingleton.HasInstance)
|
|
149
190
|
{
|
|
@@ -4,6 +4,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Serialization
|
|
|
4
4
|
using System.Collections.Generic;
|
|
5
5
|
using System.Linq;
|
|
6
6
|
using System.Runtime.Serialization;
|
|
7
|
+
using System.Text.Json;
|
|
7
8
|
using System.Text.Json.Serialization;
|
|
8
9
|
using NUnit.Framework;
|
|
9
10
|
using UnityEngine;
|
|
@@ -42,9 +43,9 @@ namespace WallstopStudios.UnityHelpers.Tests.Serialization
|
|
|
42
43
|
using System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(json);
|
|
43
44
|
System.Text.Json.JsonElement root = doc.RootElement;
|
|
44
45
|
Assert.AreEqual(System.Text.Json.JsonValueKind.Object, root.ValueKind);
|
|
45
|
-
Assert.True(root.TryGetProperty("name", out
|
|
46
|
-
Assert.True(root.TryGetProperty("type", out
|
|
47
|
-
Assert.True(root.TryGetProperty("instanceId", out
|
|
46
|
+
Assert.True(root.TryGetProperty("name", out JsonElement name));
|
|
47
|
+
Assert.True(root.TryGetProperty("type", out JsonElement type));
|
|
48
|
+
Assert.True(root.TryGetProperty("instanceId", out JsonElement id));
|
|
48
49
|
Assert.AreEqual("Test GameObject", name.GetString());
|
|
49
50
|
StringAssert.Contains("UnityEngine.GameObject", type.GetString());
|
|
50
51
|
Assert.AreEqual(expectedId, id.GetInt32());
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Tags
|
|
2
|
+
{
|
|
3
|
+
using System.Text.Json;
|
|
4
|
+
using NUnit.Framework;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
using WallstopStudios.UnityHelpers.Tags;
|
|
7
|
+
|
|
8
|
+
[TestFixture]
|
|
9
|
+
public sealed class AttributeEffectTests : AttributeTagsTestBase
|
|
10
|
+
{
|
|
11
|
+
[Test]
|
|
12
|
+
public void HumanReadableDescriptionFormatsAllModificationTypes()
|
|
13
|
+
{
|
|
14
|
+
AttributeEffect effect = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
15
|
+
effect.name = "Composite";
|
|
16
|
+
effect.modifications.Add(
|
|
17
|
+
new AttributeModification
|
|
18
|
+
{
|
|
19
|
+
attribute = "health",
|
|
20
|
+
action = ModificationAction.Addition,
|
|
21
|
+
value = 5f,
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
effect.modifications.Add(
|
|
25
|
+
new AttributeModification
|
|
26
|
+
{
|
|
27
|
+
attribute = "attack_speed",
|
|
28
|
+
action = ModificationAction.Multiplication,
|
|
29
|
+
value = 1.5f,
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
effect.modifications.Add(
|
|
33
|
+
new AttributeModification
|
|
34
|
+
{
|
|
35
|
+
attribute = "armor",
|
|
36
|
+
action = ModificationAction.Override,
|
|
37
|
+
value = 10f,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
string description = effect.HumanReadableDescription;
|
|
42
|
+
Assert.AreEqual("+5 Health, +50% Attack Speed, 10 Armor", description);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[Test]
|
|
46
|
+
public void HumanReadableDescriptionSkipsNeutralModifications()
|
|
47
|
+
{
|
|
48
|
+
AttributeEffect effect = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
49
|
+
effect.modifications.Add(
|
|
50
|
+
new AttributeModification
|
|
51
|
+
{
|
|
52
|
+
attribute = "health",
|
|
53
|
+
action = ModificationAction.Addition,
|
|
54
|
+
value = 0f,
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
effect.modifications.Add(
|
|
58
|
+
new AttributeModification
|
|
59
|
+
{
|
|
60
|
+
attribute = "speed",
|
|
61
|
+
action = ModificationAction.Multiplication,
|
|
62
|
+
value = 1f,
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
Assert.IsEmpty(effect.HumanReadableDescription);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[Test]
|
|
70
|
+
public void ToStringSerializesSummaryAndCollections()
|
|
71
|
+
{
|
|
72
|
+
AttributeEffect effect = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
73
|
+
effect.name = "JsonEffect";
|
|
74
|
+
effect.durationType = ModifierDurationType.Duration;
|
|
75
|
+
effect.duration = 3.25f;
|
|
76
|
+
effect.resetDurationOnReapplication = true;
|
|
77
|
+
effect.modifications.Add(
|
|
78
|
+
new AttributeModification
|
|
79
|
+
{
|
|
80
|
+
attribute = "health",
|
|
81
|
+
action = ModificationAction.Addition,
|
|
82
|
+
value = 10f,
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
effect.effectTags.Add("Buff");
|
|
86
|
+
|
|
87
|
+
GameObject cosmeticHolder = Track(new GameObject("Glow", typeof(CosmeticEffectData)));
|
|
88
|
+
CosmeticEffectData cosmeticData = cosmeticHolder.GetComponent<CosmeticEffectData>();
|
|
89
|
+
effect.cosmeticEffects.Add(cosmeticData);
|
|
90
|
+
|
|
91
|
+
using JsonDocument document = JsonDocument.Parse(effect.ToString());
|
|
92
|
+
JsonElement root = document.RootElement;
|
|
93
|
+
Assert.AreEqual(
|
|
94
|
+
effect.HumanReadableDescription,
|
|
95
|
+
root.GetProperty("Description").GetString()
|
|
96
|
+
);
|
|
97
|
+
Assert.AreEqual("Duration", root.GetProperty("durationType").GetString());
|
|
98
|
+
Assert.AreEqual(3.25f, root.GetProperty("duration").GetSingle());
|
|
99
|
+
Assert.AreEqual("Buff", root.GetProperty("tags")[0].GetString());
|
|
100
|
+
Assert.AreEqual("Glow", root.GetProperty("CosmeticEffects")[0].GetString());
|
|
101
|
+
Assert.AreEqual(1, root.GetProperty("modifications").GetArrayLength());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
[Test]
|
|
105
|
+
public void EqualsRequiresMatchingState()
|
|
106
|
+
{
|
|
107
|
+
AttributeEffect left = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
108
|
+
AttributeEffect right = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
109
|
+
left.name = right.name = "Stack";
|
|
110
|
+
left.durationType = right.durationType = ModifierDurationType.Duration;
|
|
111
|
+
left.duration = right.duration = 2f;
|
|
112
|
+
left.resetDurationOnReapplication = right.resetDurationOnReapplication = false;
|
|
113
|
+
|
|
114
|
+
AttributeModification modification = new()
|
|
115
|
+
{
|
|
116
|
+
attribute = "health",
|
|
117
|
+
action = ModificationAction.Addition,
|
|
118
|
+
value = 5f,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
left.modifications.Add(modification);
|
|
122
|
+
right.modifications.Add(modification);
|
|
123
|
+
Assert.IsTrue(left.Equals(right));
|
|
124
|
+
|
|
125
|
+
right.modifications[0] = new AttributeModification
|
|
126
|
+
{
|
|
127
|
+
attribute = "health",
|
|
128
|
+
action = ModificationAction.Addition,
|
|
129
|
+
value = 10f,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
Assert.IsFalse(left.Equals(right));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Tags
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Text.Json;
|
|
5
|
+
using NUnit.Framework;
|
|
6
|
+
using WallstopStudios.UnityHelpers.Core.Serialization;
|
|
7
|
+
using WallstopStudios.UnityHelpers.Tags;
|
|
8
|
+
using WallstopStudios.UnityHelpers.Tests.TestUtils;
|
|
9
|
+
|
|
10
|
+
public abstract class AttributeTagsTestBase : CommonTestBase
|
|
11
|
+
{
|
|
12
|
+
protected static void ResetEffectHandleId(long value = 0)
|
|
13
|
+
{
|
|
14
|
+
EffectHandle.Id = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[TestFixture]
|
|
19
|
+
public sealed class AttributeModificationTests
|
|
20
|
+
{
|
|
21
|
+
[Test]
|
|
22
|
+
public void EqualityOperatorsRespectFields()
|
|
23
|
+
{
|
|
24
|
+
AttributeModification baseline = new()
|
|
25
|
+
{
|
|
26
|
+
attribute = "health",
|
|
27
|
+
action = ModificationAction.Multiplication,
|
|
28
|
+
value = 1.5f,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
AttributeModification clone = baseline;
|
|
32
|
+
Assert.IsTrue(baseline == clone);
|
|
33
|
+
Assert.IsFalse(baseline != clone);
|
|
34
|
+
Assert.AreEqual(baseline, clone);
|
|
35
|
+
Assert.AreEqual(baseline.GetHashCode(), clone.GetHashCode());
|
|
36
|
+
|
|
37
|
+
AttributeModification differentAttribute = baseline;
|
|
38
|
+
differentAttribute.attribute = "armor";
|
|
39
|
+
Assert.IsFalse(baseline == differentAttribute);
|
|
40
|
+
Assert.IsTrue(baseline != differentAttribute);
|
|
41
|
+
|
|
42
|
+
AttributeModification differentAction = baseline;
|
|
43
|
+
differentAction.action = ModificationAction.Addition;
|
|
44
|
+
Assert.IsFalse(baseline.Equals(differentAction));
|
|
45
|
+
|
|
46
|
+
AttributeModification differentValue = baseline;
|
|
47
|
+
differentValue.value = 2f;
|
|
48
|
+
Assert.IsFalse(baseline.Equals(differentValue));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[Test]
|
|
52
|
+
public void ToStringSerializesAllFields()
|
|
53
|
+
{
|
|
54
|
+
AttributeModification modification = new()
|
|
55
|
+
{
|
|
56
|
+
attribute = "health",
|
|
57
|
+
action = ModificationAction.Override,
|
|
58
|
+
value = 42.5f,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
using JsonDocument document = JsonDocument.Parse(modification.ToString());
|
|
62
|
+
JsonElement root = document.RootElement;
|
|
63
|
+
Assert.AreEqual(
|
|
64
|
+
"health",
|
|
65
|
+
root.GetProperty(nameof(AttributeModification.attribute)).GetString()
|
|
66
|
+
);
|
|
67
|
+
Assert.AreEqual(
|
|
68
|
+
"Override",
|
|
69
|
+
root.GetProperty(nameof(AttributeModification.action)).GetString()
|
|
70
|
+
);
|
|
71
|
+
Assert.AreEqual(
|
|
72
|
+
42.5f,
|
|
73
|
+
root.GetProperty(nameof(AttributeModification.value)).GetSingle()
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[Test]
|
|
78
|
+
public void SystemTextJsonRoundtripPreservesFields()
|
|
79
|
+
{
|
|
80
|
+
AttributeModification modification = new(
|
|
81
|
+
"armor",
|
|
82
|
+
ModificationAction.Multiplication,
|
|
83
|
+
1.25f
|
|
84
|
+
);
|
|
85
|
+
string json = Serializer.JsonStringify(modification);
|
|
86
|
+
AttributeModification clone = Serializer.JsonDeserialize<AttributeModification>(json);
|
|
87
|
+
|
|
88
|
+
Assert.AreEqual(modification, clone);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[Test]
|
|
92
|
+
public void ProtoBufRoundtripPreservesFields()
|
|
93
|
+
{
|
|
94
|
+
AttributeModification modification = new("speed", ModificationAction.Addition, -3f);
|
|
95
|
+
byte[] payload = Serializer.ProtoSerialize(modification);
|
|
96
|
+
AttributeModification clone = Serializer.ProtoDeserialize<AttributeModification>(
|
|
97
|
+
payload
|
|
98
|
+
);
|
|
99
|
+
Assert.AreEqual(modification, clone);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[Test]
|
|
103
|
+
public void CompareToOrdersBasedOnAction()
|
|
104
|
+
{
|
|
105
|
+
AttributeModification addition = new("health", ModificationAction.Addition, 10f);
|
|
106
|
+
AttributeModification multiplication = new(
|
|
107
|
+
"health",
|
|
108
|
+
ModificationAction.Multiplication,
|
|
109
|
+
2f
|
|
110
|
+
);
|
|
111
|
+
AttributeModification overrideValue = new("health", ModificationAction.Override, 0f);
|
|
112
|
+
|
|
113
|
+
AttributeModification[] unsorted = { overrideValue, multiplication, addition };
|
|
114
|
+
|
|
115
|
+
Array.Sort(unsorted);
|
|
116
|
+
|
|
117
|
+
Assert.AreEqual(addition, unsorted[0], "Addition should be applied first when sorted.");
|
|
118
|
+
Assert.AreEqual(
|
|
119
|
+
multiplication,
|
|
120
|
+
unsorted[1],
|
|
121
|
+
"Multiplication should appear after additions when sorted."
|
|
122
|
+
);
|
|
123
|
+
Assert.AreEqual(
|
|
124
|
+
overrideValue,
|
|
125
|
+
unsorted[2],
|
|
126
|
+
"Override should be processed last when sorted."
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[Test]
|
|
131
|
+
public void CompareToObjectReturnsMinusOneForNonAttributeModification()
|
|
132
|
+
{
|
|
133
|
+
AttributeModification addition = new("health", ModificationAction.Addition, 5f);
|
|
134
|
+
Assert.AreEqual(-1, addition.CompareTo("not a modification"));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|