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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Tests.Tags
|
|
2
2
|
{
|
|
3
3
|
using System.Collections;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Text.RegularExpressions;
|
|
4
6
|
using NUnit.Framework;
|
|
5
7
|
using UnityEngine;
|
|
6
8
|
using UnityEngine.TestTools;
|
|
@@ -15,6 +17,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Tags
|
|
|
15
17
|
{
|
|
16
18
|
ResetEffectHandleId();
|
|
17
19
|
RecordingCosmeticComponent.ResetCounters();
|
|
20
|
+
RecordingEffectBehavior.Reset();
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
[UnityTest]
|
|
@@ -238,6 +241,812 @@ namespace WallstopStudios.UnityHelpers.Tests.Tags
|
|
|
238
241
|
Assert.AreEqual(1, RecordingCosmeticComponent.RemovedCount);
|
|
239
242
|
}
|
|
240
243
|
|
|
244
|
+
[UnityTest]
|
|
245
|
+
public IEnumerator IsEffectActiveReflectsState()
|
|
246
|
+
{
|
|
247
|
+
(
|
|
248
|
+
GameObject entity,
|
|
249
|
+
EffectHandler handler,
|
|
250
|
+
TestAttributesComponent attributes,
|
|
251
|
+
TagHandler tags
|
|
252
|
+
) = CreateEntity();
|
|
253
|
+
yield return null;
|
|
254
|
+
|
|
255
|
+
AttributeEffect effect = CreateEffect("Buff");
|
|
256
|
+
Assert.IsFalse(handler.IsEffectActive(effect));
|
|
257
|
+
|
|
258
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
259
|
+
Assert.IsTrue(handler.IsEffectActive(effect));
|
|
260
|
+
|
|
261
|
+
handler.RemoveEffect(handle);
|
|
262
|
+
Assert.IsFalse(handler.IsEffectActive(effect));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
[UnityTest]
|
|
266
|
+
public IEnumerator GetEffectStackCountSupportsMultipleHandles()
|
|
267
|
+
{
|
|
268
|
+
(
|
|
269
|
+
GameObject entity,
|
|
270
|
+
EffectHandler handler,
|
|
271
|
+
TestAttributesComponent attributes,
|
|
272
|
+
TagHandler tags
|
|
273
|
+
) = CreateEntity();
|
|
274
|
+
yield return null;
|
|
275
|
+
|
|
276
|
+
AttributeEffect effect = CreateEffect(
|
|
277
|
+
"Stacking",
|
|
278
|
+
e =>
|
|
279
|
+
{
|
|
280
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
281
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
286
|
+
EffectHandle second = handler.ApplyEffect(effect).Value;
|
|
287
|
+
|
|
288
|
+
Assert.AreEqual(2, handler.GetEffectStackCount(effect));
|
|
289
|
+
|
|
290
|
+
handler.RemoveEffect(first);
|
|
291
|
+
Assert.AreEqual(1, handler.GetEffectStackCount(effect));
|
|
292
|
+
|
|
293
|
+
handler.RemoveEffect(second);
|
|
294
|
+
Assert.AreEqual(0, handler.GetEffectStackCount(effect));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
[UnityTest]
|
|
298
|
+
public IEnumerator StackingModeIgnoreReturnsExistingHandle()
|
|
299
|
+
{
|
|
300
|
+
(
|
|
301
|
+
GameObject entity,
|
|
302
|
+
EffectHandler handler,
|
|
303
|
+
TestAttributesComponent attributes,
|
|
304
|
+
TagHandler tags
|
|
305
|
+
) = CreateEntity();
|
|
306
|
+
yield return null;
|
|
307
|
+
|
|
308
|
+
AttributeEffect effect = CreateEffect(
|
|
309
|
+
"Ignore",
|
|
310
|
+
e =>
|
|
311
|
+
{
|
|
312
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
313
|
+
e.stackingMode = EffectStackingMode.Ignore;
|
|
314
|
+
e.modifications.Add(
|
|
315
|
+
new AttributeModification
|
|
316
|
+
{
|
|
317
|
+
attribute = nameof(TestAttributesComponent.health),
|
|
318
|
+
action = ModificationAction.Addition,
|
|
319
|
+
value = 5f,
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
326
|
+
float afterFirst = attributes.health.CurrentValue;
|
|
327
|
+
|
|
328
|
+
EffectHandle? second = handler.ApplyEffect(effect);
|
|
329
|
+
Assert.IsTrue(second.HasValue);
|
|
330
|
+
Assert.AreEqual(first, second.Value);
|
|
331
|
+
Assert.AreEqual(afterFirst, attributes.health.CurrentValue);
|
|
332
|
+
|
|
333
|
+
handler.RemoveEffect(first);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
[UnityTest]
|
|
337
|
+
public IEnumerator RefreshModeWithoutResetDurationPreservesTimer()
|
|
338
|
+
{
|
|
339
|
+
(
|
|
340
|
+
GameObject entity,
|
|
341
|
+
EffectHandler handler,
|
|
342
|
+
TestAttributesComponent attributes,
|
|
343
|
+
TagHandler tags
|
|
344
|
+
) = CreateEntity();
|
|
345
|
+
yield return null;
|
|
346
|
+
|
|
347
|
+
AttributeEffect effect = CreateEffect(
|
|
348
|
+
"RefreshNoReset",
|
|
349
|
+
e =>
|
|
350
|
+
{
|
|
351
|
+
e.duration = 0.3f;
|
|
352
|
+
e.stackingMode = EffectStackingMode.Refresh;
|
|
353
|
+
e.resetDurationOnReapplication = false;
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
358
|
+
yield return new WaitForSeconds(0.05f);
|
|
359
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float beforeReapply));
|
|
360
|
+
|
|
361
|
+
EffectHandle? reapplied = handler.ApplyEffect(effect);
|
|
362
|
+
Assert.IsTrue(reapplied.HasValue);
|
|
363
|
+
Assert.AreEqual(handle, reapplied.Value);
|
|
364
|
+
|
|
365
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float afterReapply));
|
|
366
|
+
Assert.LessOrEqual(afterReapply, beforeReapply + 0.01f);
|
|
367
|
+
|
|
368
|
+
handler.RemoveEffect(handle);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
[UnityTest]
|
|
372
|
+
public IEnumerator CustomStackGroupStackAcrossAssets()
|
|
373
|
+
{
|
|
374
|
+
(
|
|
375
|
+
GameObject entity,
|
|
376
|
+
EffectHandler handler,
|
|
377
|
+
TestAttributesComponent attributes,
|
|
378
|
+
TagHandler tags
|
|
379
|
+
) = CreateEntity();
|
|
380
|
+
yield return null;
|
|
381
|
+
|
|
382
|
+
AttributeEffect effectA = CreateEffect(
|
|
383
|
+
"GroupA",
|
|
384
|
+
e =>
|
|
385
|
+
{
|
|
386
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
387
|
+
e.stackGroup = EffectStackGroup.CustomKey;
|
|
388
|
+
e.stackGroupKey = "shared";
|
|
389
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
AttributeEffect effectB = CreateEffect(
|
|
394
|
+
"GroupB",
|
|
395
|
+
e =>
|
|
396
|
+
{
|
|
397
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
398
|
+
e.stackGroup = EffectStackGroup.CustomKey;
|
|
399
|
+
e.stackGroupKey = "shared";
|
|
400
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
EffectHandle a1 = handler.ApplyEffect(effectA).Value;
|
|
405
|
+
EffectHandle b1 = handler.ApplyEffect(effectB).Value;
|
|
406
|
+
EffectHandle a2 = handler.ApplyEffect(effectA).Value;
|
|
407
|
+
|
|
408
|
+
List<EffectHandle> active = handler.GetActiveEffects();
|
|
409
|
+
Assert.AreEqual(3, active.Count);
|
|
410
|
+
Assert.AreEqual(2, handler.GetEffectStackCount(effectA));
|
|
411
|
+
Assert.AreEqual(1, handler.GetEffectStackCount(effectB));
|
|
412
|
+
|
|
413
|
+
handler.RemoveAllEffects();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
[UnityTest]
|
|
417
|
+
public IEnumerator StackedTagsPersistUntilFinalStackRemoved()
|
|
418
|
+
{
|
|
419
|
+
(
|
|
420
|
+
GameObject entity,
|
|
421
|
+
EffectHandler handler,
|
|
422
|
+
TestAttributesComponent attributes,
|
|
423
|
+
TagHandler tags
|
|
424
|
+
) = CreateEntity();
|
|
425
|
+
yield return null;
|
|
426
|
+
|
|
427
|
+
AttributeEffect effect = CreateEffect(
|
|
428
|
+
"Tagged",
|
|
429
|
+
e =>
|
|
430
|
+
{
|
|
431
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
432
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
433
|
+
e.effectTags.Add("Shielded");
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
438
|
+
EffectHandle second = handler.ApplyEffect(effect).Value;
|
|
439
|
+
|
|
440
|
+
Assert.IsTrue(tags.HasTag("Shielded"));
|
|
441
|
+
|
|
442
|
+
handler.RemoveEffect(first);
|
|
443
|
+
Assert.IsTrue(tags.HasTag("Shielded"));
|
|
444
|
+
|
|
445
|
+
handler.RemoveEffect(second);
|
|
446
|
+
Assert.IsFalse(tags.HasTag("Shielded"));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
[UnityTest]
|
|
450
|
+
public IEnumerator GetActiveEffectsPopulatesBuffer()
|
|
451
|
+
{
|
|
452
|
+
(
|
|
453
|
+
GameObject entity,
|
|
454
|
+
EffectHandler handler,
|
|
455
|
+
TestAttributesComponent attributes,
|
|
456
|
+
TagHandler tags
|
|
457
|
+
) = CreateEntity();
|
|
458
|
+
yield return null;
|
|
459
|
+
|
|
460
|
+
AttributeEffect effect = CreateEffect(
|
|
461
|
+
"Active",
|
|
462
|
+
e =>
|
|
463
|
+
{
|
|
464
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
465
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
470
|
+
EffectHandle second = handler.ApplyEffect(effect).Value;
|
|
471
|
+
|
|
472
|
+
List<EffectHandle> buffer = new();
|
|
473
|
+
handler.GetActiveEffects(buffer);
|
|
474
|
+
CollectionAssert.AreEquivalent(new[] { first, second }, buffer);
|
|
475
|
+
|
|
476
|
+
handler.RemoveEffect(first);
|
|
477
|
+
buffer.Clear();
|
|
478
|
+
handler.GetActiveEffects(buffer);
|
|
479
|
+
CollectionAssert.AreEqual(new[] { second }, buffer);
|
|
480
|
+
|
|
481
|
+
handler.RemoveEffect(second);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
[UnityTest]
|
|
485
|
+
public IEnumerator PeriodicEffectHonorsInitialDelayAndMaxTicks()
|
|
486
|
+
{
|
|
487
|
+
(
|
|
488
|
+
GameObject entity,
|
|
489
|
+
EffectHandler handler,
|
|
490
|
+
TestAttributesComponent attributes,
|
|
491
|
+
TagHandler tags
|
|
492
|
+
) = CreateEntity();
|
|
493
|
+
yield return null;
|
|
494
|
+
|
|
495
|
+
AttributeEffect effect = CreateEffect(
|
|
496
|
+
"PeriodicLimited",
|
|
497
|
+
e =>
|
|
498
|
+
{
|
|
499
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
500
|
+
PeriodicEffectDefinition definition = new()
|
|
501
|
+
{
|
|
502
|
+
initialDelay = 0.05f,
|
|
503
|
+
interval = 0.05f,
|
|
504
|
+
maxTicks = 2,
|
|
505
|
+
};
|
|
506
|
+
definition.modifications.Add(
|
|
507
|
+
new AttributeModification
|
|
508
|
+
{
|
|
509
|
+
attribute = nameof(TestAttributesComponent.health),
|
|
510
|
+
action = ModificationAction.Addition,
|
|
511
|
+
value = -10f,
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
e.periodicEffects.Add(definition);
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
519
|
+
yield return null;
|
|
520
|
+
Assert.AreEqual(100f, attributes.health.CurrentValue, 0.01f);
|
|
521
|
+
|
|
522
|
+
yield return new WaitForSeconds(0.03f);
|
|
523
|
+
Assert.AreEqual(100f, attributes.health.CurrentValue, 0.01f);
|
|
524
|
+
|
|
525
|
+
yield return new WaitForSeconds(0.06f);
|
|
526
|
+
Assert.AreEqual(90f, attributes.health.CurrentValue, 0.01f);
|
|
527
|
+
|
|
528
|
+
yield return new WaitForSeconds(0.06f);
|
|
529
|
+
Assert.AreEqual(80f, attributes.health.CurrentValue, 0.01f);
|
|
530
|
+
|
|
531
|
+
yield return new WaitForSeconds(0.06f);
|
|
532
|
+
Assert.AreEqual(80f, attributes.health.CurrentValue, 0.01f);
|
|
533
|
+
|
|
534
|
+
handler.RemoveEffect(handle);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
[UnityTest]
|
|
538
|
+
public IEnumerator PeriodicEffectUnlimitedTicksStopOnRemoval()
|
|
539
|
+
{
|
|
540
|
+
(
|
|
541
|
+
GameObject entity,
|
|
542
|
+
EffectHandler handler,
|
|
543
|
+
TestAttributesComponent attributes,
|
|
544
|
+
TagHandler tags
|
|
545
|
+
) = CreateEntity();
|
|
546
|
+
yield return null;
|
|
547
|
+
|
|
548
|
+
AttributeEffect effect = CreateEffect(
|
|
549
|
+
"PeriodicUnlimited",
|
|
550
|
+
e =>
|
|
551
|
+
{
|
|
552
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
553
|
+
PeriodicEffectDefinition definition = new() { interval = 0.05f, maxTicks = 0 };
|
|
554
|
+
definition.modifications.Add(
|
|
555
|
+
new AttributeModification
|
|
556
|
+
{
|
|
557
|
+
attribute = nameof(TestAttributesComponent.health),
|
|
558
|
+
action = ModificationAction.Addition,
|
|
559
|
+
value = -5f,
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
e.periodicEffects.Add(definition);
|
|
563
|
+
}
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
567
|
+
yield return new WaitForSeconds(0.16f);
|
|
568
|
+
float afterTicks = attributes.health.CurrentValue;
|
|
569
|
+
Assert.Less(afterTicks, 100f);
|
|
570
|
+
|
|
571
|
+
handler.RemoveEffect(handle);
|
|
572
|
+
float afterRemoval = attributes.health.CurrentValue;
|
|
573
|
+
yield return new WaitForSeconds(0.1f);
|
|
574
|
+
Assert.AreEqual(afterRemoval, attributes.health.CurrentValue, 0.01f);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
[UnityTest]
|
|
578
|
+
public IEnumerator MultiplePeriodicDefinitionsAffectAttributesIndependently()
|
|
579
|
+
{
|
|
580
|
+
(
|
|
581
|
+
GameObject entity,
|
|
582
|
+
EffectHandler handler,
|
|
583
|
+
TestAttributesComponent attributes,
|
|
584
|
+
TagHandler tags
|
|
585
|
+
) = CreateEntity();
|
|
586
|
+
yield return null;
|
|
587
|
+
|
|
588
|
+
AttributeEffect effect = CreateEffect(
|
|
589
|
+
"PeriodicMulti",
|
|
590
|
+
e =>
|
|
591
|
+
{
|
|
592
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
593
|
+
|
|
594
|
+
PeriodicEffectDefinition damage = new()
|
|
595
|
+
{
|
|
596
|
+
initialDelay = 0.05f,
|
|
597
|
+
interval = 0.05f,
|
|
598
|
+
maxTicks = 2,
|
|
599
|
+
};
|
|
600
|
+
damage.modifications.Add(
|
|
601
|
+
new AttributeModification
|
|
602
|
+
{
|
|
603
|
+
attribute = nameof(TestAttributesComponent.health),
|
|
604
|
+
action = ModificationAction.Addition,
|
|
605
|
+
value = -5f,
|
|
606
|
+
}
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
PeriodicEffectDefinition armorGain = new()
|
|
610
|
+
{
|
|
611
|
+
initialDelay = 0.02f,
|
|
612
|
+
interval = 0.1f,
|
|
613
|
+
maxTicks = 3,
|
|
614
|
+
};
|
|
615
|
+
armorGain.modifications.Add(
|
|
616
|
+
new AttributeModification
|
|
617
|
+
{
|
|
618
|
+
attribute = nameof(TestAttributesComponent.armor),
|
|
619
|
+
action = ModificationAction.Addition,
|
|
620
|
+
value = 1f,
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
e.periodicEffects.Add(damage);
|
|
625
|
+
e.periodicEffects.Add(armorGain);
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
630
|
+
|
|
631
|
+
yield return new WaitForSeconds(0.06f);
|
|
632
|
+
Assert.AreEqual(95f, attributes.health.CurrentValue, 0.01f);
|
|
633
|
+
Assert.AreEqual(51f, attributes.armor.CurrentValue, 0.01f);
|
|
634
|
+
|
|
635
|
+
yield return new WaitForSeconds(0.1f);
|
|
636
|
+
Assert.AreEqual(90f, attributes.health.CurrentValue, 0.01f);
|
|
637
|
+
Assert.AreEqual(52f, attributes.armor.CurrentValue, 0.01f);
|
|
638
|
+
|
|
639
|
+
yield return new WaitForSeconds(0.2f);
|
|
640
|
+
Assert.AreEqual(90f, attributes.health.CurrentValue, 0.01f);
|
|
641
|
+
Assert.AreEqual(53f, attributes.armor.CurrentValue, 0.01f);
|
|
642
|
+
|
|
643
|
+
handler.RemoveEffect(handle);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
[UnityTest]
|
|
647
|
+
public IEnumerator TryGetRemainingDurationReportsTime()
|
|
648
|
+
{
|
|
649
|
+
(
|
|
650
|
+
GameObject entity,
|
|
651
|
+
EffectHandler handler,
|
|
652
|
+
TestAttributesComponent attributes,
|
|
653
|
+
TagHandler tags
|
|
654
|
+
) = CreateEntity();
|
|
655
|
+
yield return null;
|
|
656
|
+
|
|
657
|
+
AttributeEffect effect = CreateEffect(
|
|
658
|
+
"Timed",
|
|
659
|
+
e =>
|
|
660
|
+
{
|
|
661
|
+
e.duration = 0.5f;
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
666
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float remaining));
|
|
667
|
+
Assert.Greater(remaining, 0f);
|
|
668
|
+
Assert.LessOrEqual(remaining, effect.duration);
|
|
669
|
+
|
|
670
|
+
handler.RemoveEffect(handle);
|
|
671
|
+
Assert.IsFalse(handler.TryGetRemainingDuration(handle, out float afterRemoval));
|
|
672
|
+
Assert.AreEqual(0f, afterRemoval);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
[UnityTest]
|
|
676
|
+
public IEnumerator EnsureHandleRefreshesDurationWhenRequested()
|
|
677
|
+
{
|
|
678
|
+
(
|
|
679
|
+
GameObject entity,
|
|
680
|
+
EffectHandler handler,
|
|
681
|
+
TestAttributesComponent attributes,
|
|
682
|
+
TagHandler tags
|
|
683
|
+
) = CreateEntity();
|
|
684
|
+
yield return null;
|
|
685
|
+
|
|
686
|
+
AttributeEffect effect = CreateEffect(
|
|
687
|
+
"Refreshable",
|
|
688
|
+
e =>
|
|
689
|
+
{
|
|
690
|
+
e.duration = 0.2f;
|
|
691
|
+
e.resetDurationOnReapplication = true;
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
696
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float initialRemaining));
|
|
697
|
+
yield return null;
|
|
698
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float beforeRefresh));
|
|
699
|
+
Assert.Less(beforeRefresh, initialRemaining);
|
|
700
|
+
|
|
701
|
+
EffectHandle? ensured = handler.EnsureHandle(effect);
|
|
702
|
+
Assert.IsTrue(ensured.HasValue);
|
|
703
|
+
Assert.AreEqual(handle, ensured.Value);
|
|
704
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float afterRefresh));
|
|
705
|
+
Assert.Greater(afterRefresh, beforeRefresh);
|
|
706
|
+
|
|
707
|
+
yield return null;
|
|
708
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float beforeNoRefresh));
|
|
709
|
+
EffectHandle? ensuredNoRefresh = handler.EnsureHandle(effect, refreshDuration: false);
|
|
710
|
+
Assert.IsTrue(ensuredNoRefresh.HasValue);
|
|
711
|
+
Assert.AreEqual(handle, ensuredNoRefresh.Value);
|
|
712
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float afterNoRefresh));
|
|
713
|
+
Assert.LessOrEqual(afterNoRefresh, beforeNoRefresh);
|
|
714
|
+
|
|
715
|
+
handler.RemoveEffect(handle);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
[UnityTest]
|
|
719
|
+
public IEnumerator RefreshEffectHonorsReapplicationPolicy()
|
|
720
|
+
{
|
|
721
|
+
(
|
|
722
|
+
GameObject entity,
|
|
723
|
+
EffectHandler handler,
|
|
724
|
+
TestAttributesComponent attributes,
|
|
725
|
+
TagHandler tags
|
|
726
|
+
) = CreateEntity();
|
|
727
|
+
yield return null;
|
|
728
|
+
|
|
729
|
+
AttributeEffect effect = CreateEffect(
|
|
730
|
+
"Policy",
|
|
731
|
+
e =>
|
|
732
|
+
{
|
|
733
|
+
e.duration = 0.3f;
|
|
734
|
+
e.resetDurationOnReapplication = false;
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
739
|
+
yield return null;
|
|
740
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float beforeRefresh));
|
|
741
|
+
|
|
742
|
+
Assert.IsFalse(handler.RefreshEffect(handle));
|
|
743
|
+
Assert.IsTrue(handler.RefreshEffect(handle, ignoreReapplicationPolicy: true));
|
|
744
|
+
Assert.IsTrue(handler.TryGetRemainingDuration(handle, out float afterRefresh));
|
|
745
|
+
Assert.Greater(afterRefresh, beforeRefresh);
|
|
746
|
+
|
|
747
|
+
handler.RemoveEffect(handle);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
[UnityTest]
|
|
751
|
+
public IEnumerator PeriodicEffectAppliesTicksAndStops()
|
|
752
|
+
{
|
|
753
|
+
(
|
|
754
|
+
GameObject entity,
|
|
755
|
+
EffectHandler handler,
|
|
756
|
+
TestAttributesComponent attributes,
|
|
757
|
+
TagHandler tags
|
|
758
|
+
) = CreateEntity();
|
|
759
|
+
yield return null;
|
|
760
|
+
|
|
761
|
+
AttributeEffect effect = CreateEffect(
|
|
762
|
+
"Periodic",
|
|
763
|
+
e =>
|
|
764
|
+
{
|
|
765
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
766
|
+
PeriodicEffectDefinition periodic = new() { interval = 0.1f, maxTicks = 3 };
|
|
767
|
+
periodic.modifications.Add(
|
|
768
|
+
new AttributeModification
|
|
769
|
+
{
|
|
770
|
+
attribute = nameof(TestAttributesComponent.health),
|
|
771
|
+
action = ModificationAction.Addition,
|
|
772
|
+
value = -10f,
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
e.periodicEffects.Add(periodic);
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
780
|
+
yield return new WaitForSeconds(0.35f);
|
|
781
|
+
Assert.AreEqual(70f, attributes.health.CurrentValue, 0.01f);
|
|
782
|
+
|
|
783
|
+
yield return new WaitForSeconds(0.2f);
|
|
784
|
+
handler.RemoveEffect(handle);
|
|
785
|
+
Assert.AreEqual(70f, attributes.health.CurrentValue, 0.01f);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
[UnityTest]
|
|
789
|
+
public IEnumerator EffectBehaviorReceivesCallbacks()
|
|
790
|
+
{
|
|
791
|
+
(
|
|
792
|
+
GameObject entity,
|
|
793
|
+
EffectHandler handler,
|
|
794
|
+
TestAttributesComponent attributes,
|
|
795
|
+
TagHandler tags
|
|
796
|
+
) = CreateEntity();
|
|
797
|
+
yield return null;
|
|
798
|
+
|
|
799
|
+
AttributeEffect effect = CreateEffect(
|
|
800
|
+
"Behavior",
|
|
801
|
+
e =>
|
|
802
|
+
{
|
|
803
|
+
e.duration = 0.25f;
|
|
804
|
+
e.periodicEffects.Add(
|
|
805
|
+
new PeriodicEffectDefinition { interval = 0.05f, maxTicks = 2 }
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
RecordingEffectBehavior behavior = Track(
|
|
811
|
+
ScriptableObject.CreateInstance<RecordingEffectBehavior>()
|
|
812
|
+
);
|
|
813
|
+
effect.behaviors.Add(behavior);
|
|
814
|
+
|
|
815
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
816
|
+
Assert.AreEqual(1, RecordingEffectBehavior.ApplyCount);
|
|
817
|
+
|
|
818
|
+
yield return null;
|
|
819
|
+
Assert.Greater(RecordingEffectBehavior.TickCount, 0);
|
|
820
|
+
|
|
821
|
+
yield return new WaitForSeconds(0.12f);
|
|
822
|
+
Assert.GreaterOrEqual(RecordingEffectBehavior.PeriodicTickCount, 1);
|
|
823
|
+
|
|
824
|
+
handler.RemoveEffect(handle);
|
|
825
|
+
Assert.AreEqual(1, RecordingEffectBehavior.RemoveCount);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
[UnityTest]
|
|
829
|
+
public IEnumerator EffectBehaviorClonesPerHandle()
|
|
830
|
+
{
|
|
831
|
+
(
|
|
832
|
+
GameObject entity,
|
|
833
|
+
EffectHandler handler,
|
|
834
|
+
TestAttributesComponent attributes,
|
|
835
|
+
TagHandler tags
|
|
836
|
+
) = CreateEntity();
|
|
837
|
+
yield return null;
|
|
838
|
+
|
|
839
|
+
AttributeEffect effect = CreateEffect(
|
|
840
|
+
"BehaviorStacks",
|
|
841
|
+
e =>
|
|
842
|
+
{
|
|
843
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
844
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
RecordingEffectBehavior behavior = Track(
|
|
849
|
+
ScriptableObject.CreateInstance<RecordingEffectBehavior>()
|
|
850
|
+
);
|
|
851
|
+
effect.behaviors.Add(behavior);
|
|
852
|
+
|
|
853
|
+
int startingInstances = RecordingEffectBehavior.InstanceCount;
|
|
854
|
+
|
|
855
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
856
|
+
Assert.AreEqual(startingInstances + 1, RecordingEffectBehavior.InstanceCount);
|
|
857
|
+
|
|
858
|
+
EffectHandle second = handler.ApplyEffect(effect).Value;
|
|
859
|
+
Assert.AreEqual(startingInstances + 2, RecordingEffectBehavior.InstanceCount);
|
|
860
|
+
Assert.AreEqual(2, RecordingEffectBehavior.ApplyCount);
|
|
861
|
+
|
|
862
|
+
handler.RemoveEffect(first);
|
|
863
|
+
handler.RemoveEffect(second);
|
|
864
|
+
Assert.AreEqual(2, RecordingEffectBehavior.RemoveCount);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
[UnityTest]
|
|
868
|
+
public IEnumerator EffectBehaviorWithoutPeriodicSkipsPeriodicCallbacks()
|
|
869
|
+
{
|
|
870
|
+
(
|
|
871
|
+
GameObject entity,
|
|
872
|
+
EffectHandler handler,
|
|
873
|
+
TestAttributesComponent attributes,
|
|
874
|
+
TagHandler tags
|
|
875
|
+
) = CreateEntity();
|
|
876
|
+
yield return null;
|
|
877
|
+
|
|
878
|
+
AttributeEffect effect = CreateEffect(
|
|
879
|
+
"BehaviorNoPeriodic",
|
|
880
|
+
e =>
|
|
881
|
+
{
|
|
882
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
RecordingEffectBehavior behavior = Track(
|
|
887
|
+
ScriptableObject.CreateInstance<RecordingEffectBehavior>()
|
|
888
|
+
);
|
|
889
|
+
effect.behaviors.Add(behavior);
|
|
890
|
+
|
|
891
|
+
EffectHandle handle = handler.ApplyEffect(effect).Value;
|
|
892
|
+
yield return new WaitForSeconds(0.1f);
|
|
893
|
+
|
|
894
|
+
Assert.AreEqual(0, RecordingEffectBehavior.PeriodicTickCount);
|
|
895
|
+
|
|
896
|
+
handler.RemoveEffect(handle);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
[UnityTest]
|
|
900
|
+
public IEnumerator StackingModeStackRespectsMaximumStacks()
|
|
901
|
+
{
|
|
902
|
+
(
|
|
903
|
+
GameObject entity,
|
|
904
|
+
EffectHandler handler,
|
|
905
|
+
TestAttributesComponent attributes,
|
|
906
|
+
TagHandler tags
|
|
907
|
+
) = CreateEntity();
|
|
908
|
+
yield return null;
|
|
909
|
+
|
|
910
|
+
AttributeEffect effect = CreateEffect(
|
|
911
|
+
"Stacking",
|
|
912
|
+
e =>
|
|
913
|
+
{
|
|
914
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
915
|
+
e.stackingMode = EffectStackingMode.Stack;
|
|
916
|
+
e.maximumStacks = 2;
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
921
|
+
EffectHandle second = handler.ApplyEffect(effect).Value;
|
|
922
|
+
EffectHandle third = handler.ApplyEffect(effect).Value;
|
|
923
|
+
|
|
924
|
+
List<EffectHandle> active = handler.GetActiveEffects();
|
|
925
|
+
Assert.AreEqual(2, active.Count);
|
|
926
|
+
CollectionAssert.DoesNotContain(active, first);
|
|
927
|
+
CollectionAssert.Contains(active, second);
|
|
928
|
+
CollectionAssert.Contains(active, third);
|
|
929
|
+
|
|
930
|
+
handler.RemoveAllEffects();
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
[UnityTest]
|
|
934
|
+
public IEnumerator InstantEffectWithPeriodicLogsWarning()
|
|
935
|
+
{
|
|
936
|
+
(
|
|
937
|
+
GameObject entity,
|
|
938
|
+
EffectHandler handler,
|
|
939
|
+
TestAttributesComponent attributes,
|
|
940
|
+
TagHandler tags
|
|
941
|
+
) = CreateEntity();
|
|
942
|
+
yield return null;
|
|
943
|
+
|
|
944
|
+
AttributeEffect effect = CreateEffect(
|
|
945
|
+
"InstantPeriodic",
|
|
946
|
+
e =>
|
|
947
|
+
{
|
|
948
|
+
e.durationType = ModifierDurationType.Instant;
|
|
949
|
+
PeriodicEffectDefinition definition = new() { interval = 0.05f, maxTicks = 1 };
|
|
950
|
+
definition.modifications.Add(
|
|
951
|
+
new AttributeModification
|
|
952
|
+
{
|
|
953
|
+
attribute = nameof(TestAttributesComponent.health),
|
|
954
|
+
action = ModificationAction.Addition,
|
|
955
|
+
value = -10f,
|
|
956
|
+
}
|
|
957
|
+
);
|
|
958
|
+
e.periodicEffects.Add(definition);
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
LogAssert.Expect(
|
|
963
|
+
LogType.Warning,
|
|
964
|
+
new Regex("defines periodic or behaviour data but is Instant")
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
EffectHandle? handle = handler.ApplyEffect(effect);
|
|
968
|
+
Assert.IsFalse(handle.HasValue);
|
|
969
|
+
|
|
970
|
+
yield return new WaitForSeconds(0.1f);
|
|
971
|
+
Assert.AreEqual(100f, attributes.health.CurrentValue, 0.01f);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
[UnityTest]
|
|
975
|
+
public IEnumerator StackingModeReplaceSwapsHandles()
|
|
976
|
+
{
|
|
977
|
+
(
|
|
978
|
+
GameObject entity,
|
|
979
|
+
EffectHandler handler,
|
|
980
|
+
TestAttributesComponent attributes,
|
|
981
|
+
TagHandler tags
|
|
982
|
+
) = CreateEntity();
|
|
983
|
+
yield return null;
|
|
984
|
+
|
|
985
|
+
AttributeEffect effect = CreateEffect(
|
|
986
|
+
"Replace",
|
|
987
|
+
e =>
|
|
988
|
+
{
|
|
989
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
990
|
+
e.stackingMode = EffectStackingMode.Replace;
|
|
991
|
+
}
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
EffectHandle first = handler.ApplyEffect(effect).Value;
|
|
995
|
+
EffectHandle second = handler.ApplyEffect(effect).Value;
|
|
996
|
+
|
|
997
|
+
List<EffectHandle> active = handler.GetActiveEffects();
|
|
998
|
+
Assert.AreEqual(1, active.Count);
|
|
999
|
+
Assert.AreEqual(second, active[0]);
|
|
1000
|
+
Assert.AreNotEqual(first, second);
|
|
1001
|
+
|
|
1002
|
+
handler.RemoveAllEffects();
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
[UnityTest]
|
|
1006
|
+
public IEnumerator CustomStackGroupSharesAcrossEffects()
|
|
1007
|
+
{
|
|
1008
|
+
(
|
|
1009
|
+
GameObject entity,
|
|
1010
|
+
EffectHandler handler,
|
|
1011
|
+
TestAttributesComponent attributes,
|
|
1012
|
+
TagHandler tags
|
|
1013
|
+
) = CreateEntity();
|
|
1014
|
+
yield return null;
|
|
1015
|
+
|
|
1016
|
+
AttributeEffect effectA = CreateEffect(
|
|
1017
|
+
"GroupA",
|
|
1018
|
+
e =>
|
|
1019
|
+
{
|
|
1020
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
1021
|
+
e.stackGroup = EffectStackGroup.CustomKey;
|
|
1022
|
+
e.stackGroupKey = "shared";
|
|
1023
|
+
e.stackingMode = EffectStackingMode.Replace;
|
|
1024
|
+
}
|
|
1025
|
+
);
|
|
1026
|
+
AttributeEffect effectB = CreateEffect(
|
|
1027
|
+
"GroupB",
|
|
1028
|
+
e =>
|
|
1029
|
+
{
|
|
1030
|
+
e.durationType = ModifierDurationType.Infinite;
|
|
1031
|
+
e.stackGroup = EffectStackGroup.CustomKey;
|
|
1032
|
+
e.stackGroupKey = "shared";
|
|
1033
|
+
e.stackingMode = EffectStackingMode.Replace;
|
|
1034
|
+
}
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
EffectHandle first = handler.ApplyEffect(effectA).Value;
|
|
1038
|
+
EffectHandle second = handler.ApplyEffect(effectB).Value;
|
|
1039
|
+
|
|
1040
|
+
List<EffectHandle> active = handler.GetActiveEffects();
|
|
1041
|
+
Assert.AreEqual(1, active.Count);
|
|
1042
|
+
Assert.AreEqual(second, active[0]);
|
|
1043
|
+
Assert.IsFalse(handler.IsEffectActive(effectA));
|
|
1044
|
+
Assert.IsTrue(handler.IsEffectActive(effectB));
|
|
1045
|
+
Assert.AreNotEqual(first, second);
|
|
1046
|
+
|
|
1047
|
+
handler.RemoveAllEffects();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
241
1050
|
private (
|
|
242
1051
|
GameObject entity,
|
|
243
1052
|
EffectHandler handler,
|