com.wallstop-studios.unity-helpers 2.0.4 → 2.1.1
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/ILIST_SORTING_PERFORMANCE.md +92 -0
- package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
- package/Docs/INDEX.md +10 -1
- package/Docs/Images/random_generators.svg +7 -7
- package/Docs/RANDOM_PERFORMANCE.md +18 -15
- 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_2D_PERFORMANCE.md +64 -64
- package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
- package/Docs/SPATIAL_TREE_SEMANTICS.md +7 -7
- package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
- package/Editor/CustomDrawers/WShowIfPropertyDrawer.cs +131 -41
- package/Editor/Utils/ScriptableObjectSingletonCreator.cs +175 -18
- package/README.md +42 -18
- package/Runtime/Core/Extension/IListExtensions.cs +720 -12
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
- package/Runtime/Core/Random/AbstractRandom.cs +52 -5
- package/Runtime/Core/Random/DotNetRandom.cs +3 -3
- package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
- package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
- package/Runtime/Core/Random/IllusionFlow.cs +3 -3
- package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
- package/Runtime/Core/Random/PcgRandom.cs +6 -6
- package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
- package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
- package/Runtime/Core/Random/RomuDuo.cs +3 -3
- package/Runtime/Core/Random/SplitMix64.cs +3 -3
- package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
- package/Runtime/Core/Random/StormDropRandom.cs +271 -0
- package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
- package/Runtime/Core/Random/UnityRandom.cs +3 -3
- package/Runtime/Core/Random/WyRandom.cs +6 -4
- package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
- package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
- package/Runtime/Tags/Attribute.cs +144 -24
- package/Runtime/Tags/AttributeEffect.cs +119 -16
- package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
- package/Runtime/Tags/AttributeModification.cs +59 -29
- 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 +385 -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/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/Tags/AttributeMetadataCacheTests.cs +192 -0
- package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
- package/{node_modules.meta → Tests/Editor/Tags.meta} +1 -1
- package/Tests/Editor/Utils/ScriptableObjectSingletonTests.cs +41 -0
- package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
- package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
- package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
- package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
- package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
- package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
- package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
- package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
- package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
- package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
- package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
- package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +4 -3
- package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
- package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
- package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
- 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/EffectBehaviorTests.cs +184 -0
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/EffectHandlerTests.cs +618 -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/package.json +1 -1
- package/scripts/lint-doc-links.ps1 +156 -11
- package/Tests/Runtime/Tags/AttributeDataTests.cs +0 -312
- /package/Tests/Runtime/Tags/{AttributeDataTests.cs.meta → AttributeModificationTests.cs.meta} +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Random
|
|
2
|
+
{
|
|
3
|
+
using WallstopStudios.UnityHelpers.Core.Random;
|
|
4
|
+
|
|
5
|
+
public sealed class FlurryBurstRandomTests : RandomTestBase
|
|
6
|
+
{
|
|
7
|
+
protected override IRandom NewRandom()
|
|
8
|
+
{
|
|
9
|
+
return new FlurryBurstRandom();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Random
|
|
2
|
+
{
|
|
3
|
+
using WallstopStudios.UnityHelpers.Core.Random;
|
|
4
|
+
|
|
5
|
+
public sealed class PhotonSpinRandomTests : RandomTestBase
|
|
6
|
+
{
|
|
7
|
+
protected override IRandom NewRandom()
|
|
8
|
+
{
|
|
9
|
+
return new PhotonSpinRandom();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -171,6 +171,20 @@ namespace WallstopStudios.UnityHelpers.Tests.Random
|
|
|
171
171
|
Assert.AreEqual(random.InternalState.State2, deserialized.InternalState.State2);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
[Test]
|
|
175
|
+
public void PhotonSpinRandomSerializesAndDeserializes()
|
|
176
|
+
{
|
|
177
|
+
PhotonSpinRandom random = new(Guid.Parse("0AF7CF4F-44F6-421E-B7DC-1ADEF9F27E19"));
|
|
178
|
+
VerifySerializationAndGeneration(random);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
[Test]
|
|
182
|
+
public void StormDropRandomSerializesAndDeserializes()
|
|
183
|
+
{
|
|
184
|
+
StormDropRandom random = new(987654321u);
|
|
185
|
+
VerifySerializationAndGeneration(random);
|
|
186
|
+
}
|
|
187
|
+
|
|
174
188
|
[Test]
|
|
175
189
|
public void UnityRandomSerializesAndDeserializes()
|
|
176
190
|
{
|
|
@@ -514,19 +514,54 @@ namespace WallstopStudios.UnityHelpers.Tests.Random
|
|
|
514
514
|
|
|
515
515
|
[Test]
|
|
516
516
|
[Parallelizable]
|
|
517
|
-
public void
|
|
517
|
+
public void JsonSerialization()
|
|
518
518
|
{
|
|
519
519
|
IRandom random = NewRandom();
|
|
520
520
|
string json = random.ToJson();
|
|
521
521
|
IRandom deserialized = Serializer.JsonDeserialize<IRandom>(json, random.GetType());
|
|
522
522
|
Assert.AreEqual(random.InternalState, deserialized.InternalState);
|
|
523
523
|
|
|
524
|
-
if (
|
|
524
|
+
if (random is not UnityRandom)
|
|
525
525
|
{
|
|
526
526
|
for (int i = 0; i < NumGeneratorChecks; ++i)
|
|
527
527
|
{
|
|
528
|
-
Assert.AreEqual(random.Next(), deserialized.Next());
|
|
529
|
-
Assert.AreEqual(
|
|
528
|
+
Assert.AreEqual(random.Next(), deserialized.Next(), "Iteration: " + i);
|
|
529
|
+
Assert.AreEqual(
|
|
530
|
+
random.InternalState,
|
|
531
|
+
deserialized.InternalState,
|
|
532
|
+
"Iteration: " + i
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
[Test]
|
|
539
|
+
[Parallelizable]
|
|
540
|
+
public void JsonSerializationWithMix()
|
|
541
|
+
{
|
|
542
|
+
for (int preMix = 1; preMix < 10; ++preMix)
|
|
543
|
+
{
|
|
544
|
+
IRandom random = NewRandom();
|
|
545
|
+
for (int j = 0; j < preMix; ++j)
|
|
546
|
+
{
|
|
547
|
+
_ = random.Next();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
string json = random.ToJson();
|
|
551
|
+
IRandom deserialized = Serializer.JsonDeserialize<IRandom>(json, random.GetType());
|
|
552
|
+
Assert.AreEqual(random.InternalState, deserialized.InternalState);
|
|
553
|
+
|
|
554
|
+
if (random is not UnityRandom)
|
|
555
|
+
{
|
|
556
|
+
for (int i = 0; i < NumGeneratorChecks; ++i)
|
|
557
|
+
{
|
|
558
|
+
Assert.AreEqual(random.Next(), deserialized.Next(), "Iteration: " + i);
|
|
559
|
+
Assert.AreEqual(
|
|
560
|
+
random.InternalState,
|
|
561
|
+
deserialized.InternalState,
|
|
562
|
+
"Iteration: " + i
|
|
563
|
+
);
|
|
564
|
+
}
|
|
530
565
|
}
|
|
531
566
|
}
|
|
532
567
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Random
|
|
2
|
+
{
|
|
3
|
+
using WallstopStudios.UnityHelpers.Core.Random;
|
|
4
|
+
|
|
5
|
+
public sealed class StormDropRandomTests : RandomTestBase
|
|
6
|
+
{
|
|
7
|
+
protected override IRandom NewRandom()
|
|
8
|
+
{
|
|
9
|
+
return new StormDropRandom();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -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());
|
|
@@ -23,7 +23,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Serialization
|
|
|
23
23
|
public void SingleImplementationRequiresRegistration()
|
|
24
24
|
{
|
|
25
25
|
IWidget original = new Widget { Id = 3, Label = "ok" };
|
|
26
|
-
byte[] data = Serializer.ProtoSerialize
|
|
26
|
+
byte[] data = Serializer.ProtoSerialize(original);
|
|
27
27
|
|
|
28
28
|
Assert.Throws<ProtoException>(
|
|
29
29
|
() => Serializer.ProtoDeserialize<IWidget>(data),
|
|
@@ -65,7 +65,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Serialization
|
|
|
65
65
|
public void AbstractBaseWithoutRegistrationThrows()
|
|
66
66
|
{
|
|
67
67
|
AbstractBase original = new DerivedA { Common = 9, ExtraA = "x" };
|
|
68
|
-
byte[] data = Serializer.ProtoSerialize
|
|
68
|
+
byte[] data = Serializer.ProtoSerialize(original, forceRuntimeType: true);
|
|
69
69
|
|
|
70
70
|
Assert.Throws<ProtoException>(
|
|
71
71
|
() => Serializer.ProtoDeserialize<AbstractBase>(data),
|
|
@@ -37,7 +37,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Serialization
|
|
|
37
37
|
{
|
|
38
38
|
IAnimal original = new Dog { Age = 5, Name = "Rex" };
|
|
39
39
|
|
|
40
|
-
byte[] data = Serializer.ProtoSerialize
|
|
40
|
+
byte[] data = Serializer.ProtoSerialize(original);
|
|
41
41
|
Serializer.RegisterProtobufRoot<IAnimal, Dog>();
|
|
42
42
|
|
|
43
43
|
IAnimal round = Serializer.ProtoDeserialize<IAnimal>(data);
|
|
@@ -51,7 +51,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Serialization
|
|
|
51
51
|
public void ObjectDeclaredUsesRuntimeTypeByDefault()
|
|
52
52
|
{
|
|
53
53
|
object original = new DerivedMsg { A = 42, B = "obj" };
|
|
54
|
-
byte[] data = Serializer.ProtoSerialize
|
|
54
|
+
byte[] data = Serializer.ProtoSerialize(original);
|
|
55
55
|
DerivedMsg round = Serializer.ProtoDeserialize<DerivedMsg>(data);
|
|
56
56
|
|
|
57
57
|
Assert.IsNotNull(round, "Deserialized instance should not be null");
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Tags
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using NUnit.Framework;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
using WallstopStudios.UnityHelpers.Tags;
|
|
7
|
+
using Attribute = WallstopStudios.UnityHelpers.Tags.Attribute;
|
|
8
|
+
|
|
9
|
+
[TestFixture]
|
|
10
|
+
public sealed class AttributeTests : AttributeTagsTestBase
|
|
11
|
+
{
|
|
12
|
+
[SetUp]
|
|
13
|
+
public void SetUp()
|
|
14
|
+
{
|
|
15
|
+
ResetEffectHandleId();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[Test]
|
|
19
|
+
public void CurrentValueReflectsBaseValueWhenUnmodified()
|
|
20
|
+
{
|
|
21
|
+
Attribute attribute = new(12f);
|
|
22
|
+
Assert.AreEqual(12f, attribute.CurrentValue);
|
|
23
|
+
Assert.AreEqual(12f, attribute.BaseValue);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[Test]
|
|
27
|
+
public void ApplyAttributeModificationWithoutHandleMutatesBase()
|
|
28
|
+
{
|
|
29
|
+
Attribute attribute = new(10f);
|
|
30
|
+
AttributeModification modification = new()
|
|
31
|
+
{
|
|
32
|
+
attribute = "health",
|
|
33
|
+
action = ModificationAction.Addition,
|
|
34
|
+
value = 5f,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
attribute.ApplyAttributeModification(modification);
|
|
38
|
+
Assert.AreEqual(15f, attribute.BaseValue);
|
|
39
|
+
Assert.AreEqual(15f, attribute.CurrentValue);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[Test]
|
|
43
|
+
public void ApplyAndRemoveAttributeModificationWithHandleRecalculates()
|
|
44
|
+
{
|
|
45
|
+
Attribute attribute = new(100f);
|
|
46
|
+
AttributeModification addition = new()
|
|
47
|
+
{
|
|
48
|
+
attribute = "health",
|
|
49
|
+
action = ModificationAction.Addition,
|
|
50
|
+
value = 25f,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
AttributeEffect effect = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
54
|
+
effect.name = "Buff";
|
|
55
|
+
EffectHandle handle = EffectHandle.CreateInstance(effect);
|
|
56
|
+
|
|
57
|
+
attribute.ApplyAttributeModification(addition, handle);
|
|
58
|
+
Assert.AreEqual(125f, attribute.CurrentValue);
|
|
59
|
+
Assert.AreEqual(100f, attribute.BaseValue);
|
|
60
|
+
|
|
61
|
+
bool removed = attribute.RemoveAttributeModification(handle);
|
|
62
|
+
Assert.IsTrue(removed);
|
|
63
|
+
Assert.AreEqual(100f, attribute.CurrentValue);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[Test]
|
|
67
|
+
public void ApplyAttributeModificationWithMultiplicationExecutesInOrder()
|
|
68
|
+
{
|
|
69
|
+
Attribute attribute = new(10f);
|
|
70
|
+
AttributeEffect effect = Track(ScriptableObject.CreateInstance<AttributeEffect>());
|
|
71
|
+
effect.name = "Stacking";
|
|
72
|
+
EffectHandle handle = EffectHandle.CreateInstance(effect);
|
|
73
|
+
|
|
74
|
+
attribute.ApplyAttributeModification(
|
|
75
|
+
new AttributeModification
|
|
76
|
+
{
|
|
77
|
+
attribute = "health",
|
|
78
|
+
action = ModificationAction.Addition,
|
|
79
|
+
value = 5f,
|
|
80
|
+
},
|
|
81
|
+
handle
|
|
82
|
+
);
|
|
83
|
+
attribute.ApplyAttributeModification(
|
|
84
|
+
new AttributeModification
|
|
85
|
+
{
|
|
86
|
+
attribute = "health",
|
|
87
|
+
action = ModificationAction.Multiplication,
|
|
88
|
+
value = 2f,
|
|
89
|
+
},
|
|
90
|
+
handle
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
Assert.AreEqual(30f, attribute.CurrentValue);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[Test]
|
|
97
|
+
public void AttributeEqualsSupportsFloatComparisons()
|
|
98
|
+
{
|
|
99
|
+
Attribute attribute = new(7.25f);
|
|
100
|
+
Assert.IsTrue(attribute.Equals(7.25f));
|
|
101
|
+
Assert.IsTrue(attribute.Equals((double)7.25f));
|
|
102
|
+
Assert.IsFalse(attribute.Equals(7.5f));
|
|
103
|
+
Assert.AreEqual("7.25", attribute.ToString());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[Test]
|
|
107
|
+
public void ClearCacheForcesRecalculation()
|
|
108
|
+
{
|
|
109
|
+
Attribute attribute = new(10f);
|
|
110
|
+
AttributeModification addition = new()
|
|
111
|
+
{
|
|
112
|
+
attribute = "health",
|
|
113
|
+
action = ModificationAction.Addition,
|
|
114
|
+
value = 5f,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
attribute.ApplyAttributeModification(addition);
|
|
118
|
+
Assert.AreEqual(15f, attribute.CurrentValue);
|
|
119
|
+
|
|
120
|
+
attribute.ClearCache();
|
|
121
|
+
Assert.AreEqual(15f, attribute.CurrentValue);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
[Test]
|
|
125
|
+
public void AddProducesHandleAndAppliesAddition()
|
|
126
|
+
{
|
|
127
|
+
Attribute attribute = new(10f);
|
|
128
|
+
|
|
129
|
+
EffectHandle handle = attribute.Add(5f);
|
|
130
|
+
Assert.AreEqual(15f, attribute.CurrentValue);
|
|
131
|
+
Assert.AreEqual(1L, handle.id);
|
|
132
|
+
|
|
133
|
+
bool removed = attribute.RemoveAttributeModification(handle);
|
|
134
|
+
Assert.IsTrue(removed);
|
|
135
|
+
Assert.AreEqual(10f, attribute.CurrentValue);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[Test]
|
|
139
|
+
public void SubtractStacksAsNegativeAddition()
|
|
140
|
+
{
|
|
141
|
+
Attribute attribute = new(20f);
|
|
142
|
+
|
|
143
|
+
EffectHandle addition = attribute.Add(5f);
|
|
144
|
+
EffectHandle subtraction = attribute.Subtract(8f);
|
|
145
|
+
EffectHandle multiplier = attribute.Multiply(2f);
|
|
146
|
+
|
|
147
|
+
Assert.AreEqual(34f, attribute.CurrentValue);
|
|
148
|
+
|
|
149
|
+
bool subtractionRemoved = attribute.RemoveAttributeModification(subtraction);
|
|
150
|
+
Assert.IsTrue(subtractionRemoved);
|
|
151
|
+
Assert.AreEqual(50f, attribute.CurrentValue);
|
|
152
|
+
|
|
153
|
+
attribute.RemoveAttributeModification(addition);
|
|
154
|
+
attribute.RemoveAttributeModification(multiplier);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
[Test]
|
|
158
|
+
public void DivideAppliesReciprocalMultiplication()
|
|
159
|
+
{
|
|
160
|
+
Attribute attribute = new(12f);
|
|
161
|
+
|
|
162
|
+
EffectHandle addition = attribute.Add(6f);
|
|
163
|
+
EffectHandle division = attribute.Divide(3f);
|
|
164
|
+
|
|
165
|
+
Assert.AreEqual(6f, attribute.CurrentValue);
|
|
166
|
+
|
|
167
|
+
bool divisionRemoved = attribute.RemoveAttributeModification(division);
|
|
168
|
+
Assert.IsTrue(divisionRemoved);
|
|
169
|
+
Assert.AreEqual(18f, attribute.CurrentValue);
|
|
170
|
+
|
|
171
|
+
attribute.RemoveAttributeModification(addition);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
[Test]
|
|
175
|
+
public void DivideThrowsWhenValueIsZero()
|
|
176
|
+
{
|
|
177
|
+
Attribute attribute = new(10f);
|
|
178
|
+
Assert.Throws<ArgumentException>(() => attribute.Divide(0f));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
[Test]
|
|
182
|
+
public void ArithmeticHelpersThrowWhenValueIsNotFinite()
|
|
183
|
+
{
|
|
184
|
+
Attribute attribute = new(5f);
|
|
185
|
+
|
|
186
|
+
Assert.Throws<ArgumentException>(() => attribute.Add(float.NaN));
|
|
187
|
+
Assert.Throws<ArgumentException>(() => attribute.Subtract(float.PositiveInfinity));
|
|
188
|
+
Assert.Throws<ArgumentException>(() => attribute.Multiply(float.NegativeInfinity));
|
|
189
|
+
Assert.Throws<ArgumentException>(() => attribute.Divide(float.PositiveInfinity));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|