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
|
@@ -9,10 +9,61 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
9
9
|
using Core.Extension;
|
|
10
10
|
using Core.Helper;
|
|
11
11
|
using UnityEngine;
|
|
12
|
+
using WallstopStudios.UnityHelpers.Core.Attributes;
|
|
13
|
+
using WallstopStudios.UnityHelpers.Utils;
|
|
12
14
|
#if ODIN_INSPECTOR
|
|
13
15
|
using Sirenix.OdinInspector;
|
|
14
16
|
#endif
|
|
15
17
|
|
|
18
|
+
/// <summary>
|
|
19
|
+
/// Determines which handles are considered the "same stack" when evaluating stacking policies.
|
|
20
|
+
/// </summary>
|
|
21
|
+
public enum EffectStackGroup
|
|
22
|
+
{
|
|
23
|
+
[Obsolete("Please use a valid EffectStackGroup instead.")]
|
|
24
|
+
None = 0,
|
|
25
|
+
|
|
26
|
+
/// <summary>
|
|
27
|
+
/// Uses the effect asset reference. Each ScriptableObject instance is its own group.
|
|
28
|
+
/// </summary>
|
|
29
|
+
Reference = 1,
|
|
30
|
+
|
|
31
|
+
/// <summary>
|
|
32
|
+
/// Uses a custom string key supplied via <see cref="AttributeEffect.stackGroupKey"/>.
|
|
33
|
+
/// Effects with matching keys share a stack regardless of asset reference.
|
|
34
|
+
/// </summary>
|
|
35
|
+
CustomKey = 2,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// <summary>
|
|
39
|
+
/// Describes how additional applications of an effect interact with existing stacks.
|
|
40
|
+
/// </summary>
|
|
41
|
+
public enum EffectStackingMode
|
|
42
|
+
{
|
|
43
|
+
[Obsolete("Please use a valid EffectStackingMode instead.")]
|
|
44
|
+
None = 0,
|
|
45
|
+
|
|
46
|
+
/// <summary>
|
|
47
|
+
/// Always create a new stack (subject to optional stack limit).
|
|
48
|
+
/// </summary>
|
|
49
|
+
Stack = 1,
|
|
50
|
+
|
|
51
|
+
/// <summary>
|
|
52
|
+
/// Reuse the first existing stack and refresh duration if possible.
|
|
53
|
+
/// </summary>
|
|
54
|
+
Refresh = 2,
|
|
55
|
+
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Remove existing stacks sharing the same group before creating a new one.
|
|
58
|
+
/// </summary>
|
|
59
|
+
Replace = 3,
|
|
60
|
+
|
|
61
|
+
/// <summary>
|
|
62
|
+
/// Ignore new applications when a stack is already active.
|
|
63
|
+
/// </summary>
|
|
64
|
+
Ignore = 4,
|
|
65
|
+
}
|
|
66
|
+
|
|
16
67
|
/// <summary>
|
|
17
68
|
/// Reusable, data‑driven bundle of stat modifications, tags, and cosmetic feedback.
|
|
18
69
|
/// Serves as the authoring unit for buffs, debuffs, and status effects.
|
|
@@ -52,6 +103,7 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
52
103
|
/// </para>
|
|
53
104
|
/// </remarks>
|
|
54
105
|
[Serializable]
|
|
106
|
+
[CreateAssetMenu(menuName = "Wallstop Studios/Unity Helpers/Attribute Effect")]
|
|
55
107
|
public sealed class AttributeEffect :
|
|
56
108
|
#if ODIN_INSPECTOR
|
|
57
109
|
SerializedScriptableObject
|
|
@@ -72,24 +124,31 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
72
124
|
/// The list of attribute modifications to apply when this effect is activated.
|
|
73
125
|
/// Each modification specifies an attribute name, action type, and value.
|
|
74
126
|
/// </summary>
|
|
75
|
-
public
|
|
127
|
+
public List<AttributeModification> modifications = new();
|
|
128
|
+
|
|
129
|
+
/// <summary>
|
|
130
|
+
/// Periodic modifier sets executed on a cadence while the effect remains active.
|
|
131
|
+
/// </summary>
|
|
132
|
+
public List<PeriodicEffectDefinition> periodicEffects = new();
|
|
76
133
|
|
|
77
134
|
/// <summary>
|
|
78
135
|
/// Specifies how long this effect should persist (Instant, Duration, or Infinite).
|
|
79
136
|
/// </summary>
|
|
80
137
|
public ModifierDurationType durationType = ModifierDurationType.Duration;
|
|
81
138
|
|
|
82
|
-
#if ODIN_INSPECTOR
|
|
83
|
-
[ShowIf("@durationType == ModifierDurationType.Duration")]
|
|
84
|
-
#endif
|
|
85
139
|
/// <summary>
|
|
86
140
|
/// The duration in seconds for this effect. Only used when <see cref="durationType"/> is <see cref="ModifierDurationType.Duration"/>.
|
|
87
141
|
/// </summary>
|
|
88
|
-
public float duration;
|
|
89
|
-
|
|
90
142
|
#if ODIN_INSPECTOR
|
|
91
143
|
[ShowIf("@durationType == ModifierDurationType.Duration")]
|
|
144
|
+
#else
|
|
145
|
+
[WShowIf(
|
|
146
|
+
nameof(durationType),
|
|
147
|
+
expectedValues: new object[] { ModifierDurationType.Duration }
|
|
148
|
+
)]
|
|
92
149
|
#endif
|
|
150
|
+
public float duration;
|
|
151
|
+
|
|
93
152
|
/// <summary>
|
|
94
153
|
/// If true, reapplying this effect while it's already active will reset the duration timer.
|
|
95
154
|
/// Only used when <see cref="durationType"/> is <see cref="ModifierDurationType.Duration"/>.
|
|
@@ -98,6 +157,14 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
98
157
|
/// A poison effect with resetDurationOnReapplication=true will restart its 5-second timer
|
|
99
158
|
/// each time the poison is reapplied, preventing stacking but extending the effect.
|
|
100
159
|
/// </example>
|
|
160
|
+
#if ODIN_INSPECTOR
|
|
161
|
+
[ShowIf("@durationType == ModifierDurationType.Duration")]
|
|
162
|
+
#else
|
|
163
|
+
[WShowIf(
|
|
164
|
+
nameof(durationType),
|
|
165
|
+
expectedValues: new object[] { ModifierDurationType.Duration }
|
|
166
|
+
)]
|
|
167
|
+
#endif
|
|
101
168
|
public bool resetDurationOnReapplication;
|
|
102
169
|
|
|
103
170
|
/// <summary>
|
|
@@ -115,11 +182,195 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
115
182
|
/// These are applied when the effect becomes active and removed when it expires.
|
|
116
183
|
/// </summary>
|
|
117
184
|
[JsonIgnore]
|
|
118
|
-
public
|
|
185
|
+
public List<CosmeticEffectData> cosmeticEffects = new();
|
|
119
186
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
/// <summary>
|
|
188
|
+
/// Custom behaviours instantiated per active handle.
|
|
189
|
+
/// </summary>
|
|
190
|
+
[JsonIgnore]
|
|
191
|
+
public List<EffectBehavior> behaviors = new();
|
|
192
|
+
|
|
193
|
+
/// <summary>
|
|
194
|
+
/// Determines how this effect groups stacks for stacking decisions.
|
|
195
|
+
/// </summary>
|
|
196
|
+
public EffectStackGroup stackGroup = EffectStackGroup.Reference;
|
|
197
|
+
|
|
198
|
+
/// <summary>
|
|
199
|
+
/// Optional stack key used when <see cref="stackGroup"/> is set to <see cref="EffectStackGroup.CustomKey"/>.
|
|
200
|
+
/// </summary>
|
|
201
|
+
public string stackGroupKey;
|
|
202
|
+
|
|
203
|
+
/// <summary>
|
|
204
|
+
/// Determines how successive applications interact with existing stacks for the same group.
|
|
205
|
+
/// </summary>
|
|
206
|
+
public EffectStackingMode stackingMode = EffectStackingMode.Refresh;
|
|
207
|
+
|
|
208
|
+
/// <summary>
|
|
209
|
+
/// Optional cap on simultaneous stacks when <see cref="stackingMode"/> is <see cref="EffectStackingMode.Stack"/>.
|
|
210
|
+
/// A value of 0 means unlimited stacks.
|
|
211
|
+
/// </summary>
|
|
212
|
+
[Min(0)]
|
|
213
|
+
public int maximumStacks;
|
|
214
|
+
|
|
215
|
+
/// <summary>
|
|
216
|
+
/// Determines whether this effect applies the specified tag.
|
|
217
|
+
/// </summary>
|
|
218
|
+
/// <param name="effectTag">The tag to check.</param>
|
|
219
|
+
/// <returns><c>true</c> if the tag is present; otherwise, <c>false</c>.</returns>
|
|
220
|
+
public bool HasTag(string effectTag)
|
|
221
|
+
{
|
|
222
|
+
if (effectTags == null || string.IsNullOrEmpty(effectTag))
|
|
223
|
+
{
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (int i = 0; i < effectTags.Count; ++i)
|
|
228
|
+
{
|
|
229
|
+
if (string.Equals(effectTags[i], effectTag, StringComparison.Ordinal))
|
|
230
|
+
{
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// <summary>
|
|
239
|
+
/// Determines whether this effect applies any of the specified tags.
|
|
240
|
+
/// </summary>
|
|
241
|
+
/// <param name="effectTagsToCheck">The tags to inspect.</param>
|
|
242
|
+
/// <returns><c>true</c> if at least one tag is applied; otherwise, <c>false</c>.</returns>
|
|
243
|
+
public bool HasAnyTag(IEnumerable<string> effectTagsToCheck)
|
|
244
|
+
{
|
|
245
|
+
if (effectTags == null || effectTagsToCheck == null)
|
|
246
|
+
{
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
switch (effectTagsToCheck)
|
|
251
|
+
{
|
|
252
|
+
case IReadOnlyList<string> list:
|
|
253
|
+
{
|
|
254
|
+
return HasAnyTag(list);
|
|
255
|
+
}
|
|
256
|
+
case HashSet<string> hashSet:
|
|
257
|
+
{
|
|
258
|
+
foreach (string candidate in hashSet)
|
|
259
|
+
{
|
|
260
|
+
if (string.IsNullOrEmpty(candidate))
|
|
261
|
+
{
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (HasTag(candidate))
|
|
266
|
+
{
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
foreach (string candidate in effectTagsToCheck)
|
|
276
|
+
{
|
|
277
|
+
if (string.IsNullOrEmpty(candidate))
|
|
278
|
+
{
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (HasTag(candidate))
|
|
283
|
+
{
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// <summary>
|
|
292
|
+
/// Determines whether this effect applies any of the specified tags.
|
|
293
|
+
/// Optimized for indexed collections.
|
|
294
|
+
/// </summary>
|
|
295
|
+
/// <param name="effectTagsToCheck">The tags to inspect.</param>
|
|
296
|
+
/// <returns><c>true</c> if at least one tag is applied; otherwise, <c>false</c>.</returns>
|
|
297
|
+
public bool HasAnyTag(IReadOnlyList<string> effectTagsToCheck)
|
|
298
|
+
{
|
|
299
|
+
if (effectTags == null || effectTagsToCheck == null)
|
|
300
|
+
{
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (int i = 0; i < effectTagsToCheck.Count; ++i)
|
|
305
|
+
{
|
|
306
|
+
string candidate = effectTagsToCheck[i];
|
|
307
|
+
if (string.IsNullOrEmpty(candidate))
|
|
308
|
+
{
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (HasTag(candidate))
|
|
313
|
+
{
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// <summary>
|
|
322
|
+
/// Determines whether this effect contains modifications for the specified attribute.
|
|
323
|
+
/// </summary>
|
|
324
|
+
/// <param name="attributeName">The attribute name to inspect.</param>
|
|
325
|
+
/// <returns><c>true</c> if the effect modifies <paramref name="attributeName"/>; otherwise, <c>false</c>.</returns>
|
|
326
|
+
public bool ModifiesAttribute(string attributeName)
|
|
327
|
+
{
|
|
328
|
+
if (modifications == null || string.IsNullOrEmpty(attributeName))
|
|
329
|
+
{
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (int i = 0; i < modifications.Count; ++i)
|
|
334
|
+
{
|
|
335
|
+
AttributeModification modification = modifications[i];
|
|
336
|
+
if (string.Equals(modification.attribute, attributeName, StringComparison.Ordinal))
|
|
337
|
+
{
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// <summary>
|
|
346
|
+
/// Copies all modifications that affect the specified attribute into the provided buffer.
|
|
347
|
+
/// </summary>
|
|
348
|
+
/// <param name="attributeName">The attribute to filter by.</param>
|
|
349
|
+
/// <param name="buffer">The destination buffer. Existing entries are preserved.</param>
|
|
350
|
+
/// <returns>The number of modifications added to <paramref name="buffer"/>.</returns>
|
|
351
|
+
public List<AttributeModification> GetModifications(
|
|
352
|
+
string attributeName,
|
|
353
|
+
List<AttributeModification> buffer = null
|
|
354
|
+
)
|
|
355
|
+
{
|
|
356
|
+
buffer ??= new List<AttributeModification>();
|
|
357
|
+
buffer.Clear();
|
|
358
|
+
if (modifications == null || string.IsNullOrEmpty(attributeName))
|
|
359
|
+
{
|
|
360
|
+
return buffer;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (int i = 0; i < modifications.Count; ++i)
|
|
364
|
+
{
|
|
365
|
+
AttributeModification modification = modifications[i];
|
|
366
|
+
if (string.Equals(modification.attribute, attributeName, StringComparison.Ordinal))
|
|
367
|
+
{
|
|
368
|
+
buffer.Add(modification);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return buffer;
|
|
373
|
+
}
|
|
123
374
|
|
|
124
375
|
/// <summary>
|
|
125
376
|
/// Converts this effect to a JSON string representation including all modifications, tags, and cosmetic effects.
|
|
@@ -138,6 +389,10 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
138
389
|
}.ToJson();
|
|
139
390
|
}
|
|
140
391
|
|
|
392
|
+
private List<string> CosmeticEffectsForJson =>
|
|
393
|
+
cosmeticEffects?.Select(cosmeticEffectData => cosmeticEffectData.name).ToList()
|
|
394
|
+
?? new List<string>(0);
|
|
395
|
+
|
|
141
396
|
private string BuildDescription()
|
|
142
397
|
{
|
|
143
398
|
if (modifications == null)
|
|
@@ -145,7 +400,9 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
145
400
|
return nameof(AttributeEffect);
|
|
146
401
|
}
|
|
147
402
|
|
|
148
|
-
StringBuilder
|
|
403
|
+
using PooledResource<StringBuilder> stringBuilderBuffer = Buffers.StringBuilder.Get(
|
|
404
|
+
out StringBuilder descriptionBuilder
|
|
405
|
+
);
|
|
149
406
|
for (int i = 0; i < modifications.Count; ++i)
|
|
150
407
|
{
|
|
151
408
|
AttributeModification modification = modifications[i];
|
|
@@ -218,6 +475,16 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
218
475
|
return descriptionBuilder.ToString();
|
|
219
476
|
}
|
|
220
477
|
|
|
478
|
+
internal EffectStackKey GetStackKey()
|
|
479
|
+
{
|
|
480
|
+
if (stackGroup == EffectStackGroup.CustomKey && !string.IsNullOrEmpty(stackGroupKey))
|
|
481
|
+
{
|
|
482
|
+
return EffectStackKey.CreateCustom(stackGroupKey);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return EffectStackKey.CreateReference(this);
|
|
486
|
+
}
|
|
487
|
+
|
|
221
488
|
/// <summary>
|
|
222
489
|
/// Determines whether this effect is equal to another effect by comparing all fields.
|
|
223
490
|
/// This is needed because deserialization creates new instances, so reference equality is insufficient.
|
|
@@ -1,62 +1,68 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Tags
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
|
+
using System.Text.Json.Serialization;
|
|
4
5
|
using Core.Extension;
|
|
5
6
|
using Core.Helper;
|
|
7
|
+
using ProtoBuf;
|
|
6
8
|
|
|
7
9
|
/// <summary>
|
|
8
|
-
/// Declarative change to an <see cref="Attribute"
|
|
9
|
-
///
|
|
10
|
+
/// Declarative change applied to an <see cref="Attribute"/>.
|
|
11
|
+
/// Each instance represents a single operation (add, multiply, or override) referenced by an <see cref="AttributeEffect"/>.
|
|
10
12
|
/// </summary>
|
|
11
13
|
/// <remarks>
|
|
14
|
+
/// <para>Key properties:</para>
|
|
15
|
+
/// <list type="bullet">
|
|
16
|
+
/// <item><description>Non-destructive: temporary handles can add/remove modifications without mutating base values.</description></item>
|
|
17
|
+
/// <item><description>Deterministic ordering: <see cref="Attribute"/> always processes Addition, then Multiplication, then Override.</description></item>
|
|
18
|
+
/// <item><description>Flexible authoring: supports both instant (permanent) and duration-based effects.</description></item>
|
|
19
|
+
/// </list>
|
|
20
|
+
/// <para>Stack processing order:</para>
|
|
21
|
+
/// <list type="number">
|
|
22
|
+
/// <item><description>Addition (value += x)</description></item>
|
|
23
|
+
/// <item><description>Multiplication (value *= x)</description></item>
|
|
24
|
+
/// <item><description>Override (value = x)</description></item>
|
|
25
|
+
/// </list>
|
|
12
26
|
/// <para>
|
|
13
|
-
///
|
|
14
|
-
///
|
|
15
|
-
/// - Clear stacking rules via action ordering
|
|
16
|
-
/// - Works with both permanent (Instant) and temporary (Duration/Infinite) effects
|
|
27
|
+
/// The <see cref="attribute"/> field must match an <see cref="Attribute"/> field on the target <see cref="AttributesComponent"/>.
|
|
28
|
+
/// The Attribute Metadata Cache generator can provide editor dropdowns to avoid typos. Unknown names are ignored at runtime.
|
|
17
29
|
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// Stacking and order: Modifications are applied in this order across a target attribute:
|
|
20
|
-
/// 1) Addition (value += x) → 2) Multiplication (value *= x) → 3) Override (value = x).
|
|
21
|
-
/// This means Overrides always win last; use with care.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// <para>
|
|
24
|
-
/// Addressing: The <see cref="attribute"/> field names an <see cref="AttributesComponent"/> field of type
|
|
25
|
-
/// <see cref="Attribute"/>. Misspelled or missing names are ignored at runtime to keep effects robust.
|
|
26
|
-
/// Use the Attribute Metadata Cache generator to populate editor dropdowns and avoid typos.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// <para>
|
|
29
|
-
/// Examples:
|
|
30
|
+
/// <para>Sample definitions:</para>
|
|
30
31
|
/// <code>
|
|
31
|
-
/// // +50 flat
|
|
32
|
+
/// // +50 flat health
|
|
32
33
|
/// new AttributeModification { attribute = "Health", action = ModificationAction.Addition, value = 50f };
|
|
33
34
|
///
|
|
34
|
-
/// // +50%
|
|
35
|
+
/// // +50% speed
|
|
35
36
|
/// new AttributeModification { attribute = "Speed", action = ModificationAction.Multiplication, value = 1.5f };
|
|
36
37
|
///
|
|
37
|
-
/// //
|
|
38
|
+
/// // Hard-set defense to 0
|
|
38
39
|
/// new AttributeModification { attribute = "Defense", action = ModificationAction.Override, value = 0f };
|
|
39
40
|
/// </code>
|
|
40
|
-
///
|
|
41
|
-
/// <
|
|
42
|
-
///
|
|
43
|
-
///
|
|
44
|
-
///
|
|
45
|
-
///
|
|
46
|
-
/// </para>
|
|
41
|
+
/// <para>Authoring tips:</para>
|
|
42
|
+
/// <list type="bullet">
|
|
43
|
+
/// <item><description>Use Addition for flat buffs/debuffs; Multiplication for percentage-style adjustments.</description></item>
|
|
44
|
+
/// <item><description>Reserve Override for hard clamps (it always executes last).</description></item>
|
|
45
|
+
/// <item><description>Negative Addition subtracts; Multiplication values below 1.0 reduce the attribute.</description></item>
|
|
46
|
+
/// </list>
|
|
47
47
|
/// </remarks>
|
|
48
48
|
[Serializable]
|
|
49
|
-
|
|
49
|
+
[ProtoContract]
|
|
50
|
+
public struct AttributeModification
|
|
51
|
+
: IEquatable<AttributeModification>,
|
|
52
|
+
IComparable<AttributeModification>,
|
|
53
|
+
IComparable
|
|
50
54
|
{
|
|
51
55
|
/// <summary>
|
|
52
56
|
/// The name of the attribute to modify. This should match a field name in an <see cref="AttributesComponent"/> subclass.
|
|
53
57
|
/// </summary>
|
|
54
58
|
[StringInList(typeof(AttributeUtilities), nameof(AttributeUtilities.GetAllAttributeNames))]
|
|
59
|
+
[ProtoMember(1)]
|
|
55
60
|
public string attribute;
|
|
56
61
|
|
|
57
62
|
/// <summary>
|
|
58
63
|
/// The type of modification action to perform (Addition, Multiplication, or Override).
|
|
59
64
|
/// </summary>
|
|
65
|
+
[ProtoMember(2)]
|
|
60
66
|
public ModificationAction action;
|
|
61
67
|
|
|
62
68
|
/// <summary>
|
|
@@ -65,8 +71,17 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
65
71
|
/// <para>- Multiplication: The multiplier to apply (e.g., 1.5 for +50%, 0.5 for -50%)</para>
|
|
66
72
|
/// <para>- Override: The new absolute value to set</para>
|
|
67
73
|
/// </summary>
|
|
74
|
+
[ProtoMember(3)]
|
|
68
75
|
public float value;
|
|
69
76
|
|
|
77
|
+
[JsonConstructor]
|
|
78
|
+
public AttributeModification(string attribute, ModificationAction action, float value)
|
|
79
|
+
{
|
|
80
|
+
this.attribute = attribute;
|
|
81
|
+
this.action = action;
|
|
82
|
+
this.value = value;
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
/// <summary>
|
|
71
86
|
/// Converts this modification to a JSON string representation.
|
|
72
87
|
/// </summary>
|
|
@@ -76,6 +91,21 @@ namespace WallstopStudios.UnityHelpers.Tags
|
|
|
76
91
|
return this.ToJson();
|
|
77
92
|
}
|
|
78
93
|
|
|
94
|
+
public int CompareTo(object obj)
|
|
95
|
+
{
|
|
96
|
+
if (obj is AttributeModification other)
|
|
97
|
+
{
|
|
98
|
+
return CompareTo(other);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return -1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public int CompareTo(AttributeModification other)
|
|
105
|
+
{
|
|
106
|
+
return ((int)action).CompareTo((int)other.action);
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
/// <summary>
|
|
80
110
|
/// Determines whether two attribute modifications are not equal.
|
|
81
111
|
/// </summary>
|