268 lines
9.4 KiB
C#
268 lines
9.4 KiB
C#
using BumpCombat.Core;
|
||
using BumpCombat.Combat;
|
||
using BumpCombat.Player;
|
||
using NUnit.Framework;
|
||
using UnityEngine;
|
||
|
||
namespace BumpCombat.Tests
|
||
{
|
||
public sealed class PlayerStatsTests
|
||
{
|
||
private GameObject owner;
|
||
private PlayerStats stats;
|
||
|
||
[SetUp]
|
||
public void SetUp()
|
||
{
|
||
owner = new GameObject("PlayerStats Tests");
|
||
stats = owner.AddComponent<PlayerStats>();
|
||
}
|
||
|
||
[TearDown]
|
||
public void TearDown()
|
||
{
|
||
Object.DestroyImmediate(owner);
|
||
}
|
||
|
||
[Test]
|
||
public void Evaluate_AppliesFlatThenSummedIncreasedThenEachMore()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"flat",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Flat,
|
||
10f));
|
||
stats.AddModifier(new StatModifier(
|
||
"increased-a",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Increased,
|
||
0.1f));
|
||
stats.AddModifier(new StatModifier(
|
||
"increased-b",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Increased,
|
||
0.2f));
|
||
stats.AddModifier(new StatModifier(
|
||
"more-a",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.More,
|
||
0.1f));
|
||
stats.AddModifier(new StatModifier(
|
||
"more-b",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.More,
|
||
0.2f));
|
||
|
||
Assert.That(
|
||
stats.Evaluate(CharacterStat.CollisionDamage, 100f),
|
||
Is.EqualTo(188.76f).Within(0.001f));
|
||
}
|
||
|
||
[Test]
|
||
public void CalculateDamage_CollisionModifierDoesNotAffectSpell()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"collision-increased",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Increased,
|
||
0.5f));
|
||
|
||
Assert.That(
|
||
stats.CalculateDamage(10f, DamageTag.Collision),
|
||
Is.EqualTo(15f).Within(0.001f));
|
||
Assert.That(
|
||
stats.CalculateDamage(10f, DamageTag.Spell),
|
||
Is.EqualTo(10f).Within(0.001f));
|
||
}
|
||
|
||
[Test]
|
||
public void CalculateDamage_AppliesSharedAttackDamageBeforeCollisionDamage()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"shared-attack-increased",
|
||
CharacterStat.AttackDamage,
|
||
ModifierOperation.Increased,
|
||
0.1f));
|
||
stats.AddModifier(new StatModifier(
|
||
"collision-increased",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Increased,
|
||
0.5f));
|
||
|
||
Assert.That(
|
||
stats.CalculateDamage(10f, DamageTag.Collision),
|
||
Is.EqualTo(16.5f).Within(0.001f));
|
||
Assert.That(
|
||
stats.CalculateDamage(10f, DamageTag.Spell),
|
||
Is.EqualTo(11f).Within(0.001f));
|
||
}
|
||
|
||
[Test]
|
||
public void CalculateDamage_PreservesFractionsUntilTheFinalHit()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"damage-flat",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Flat,
|
||
0.8f));
|
||
stats.AddModifier(new StatModifier(
|
||
"damage-increased",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.Increased,
|
||
0.25f));
|
||
stats.AddModifier(new StatModifier(
|
||
"damage-more",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.More,
|
||
0.2f));
|
||
|
||
float calculatedDamage = stats.CalculateDamage(10f, DamageTag.Collision);
|
||
Assert.That(calculatedDamage, Is.EqualTo(16.2f).Within(0.001f));
|
||
Assert.That(
|
||
stats.Evaluate(CharacterStat.CollisionDamage, 10f),
|
||
Is.EqualTo(calculatedDamage).Within(0.001f),
|
||
"Damage stat evaluation and the public damage API must share the same calculation.");
|
||
Assert.That(DamageCalculator.FinalizeDamage(calculatedDamage), Is.EqualTo(16f));
|
||
Assert.That(
|
||
stats.CalculateDamage(10.9f, DamageTag.Spell),
|
||
Is.EqualTo(10.9f).Within(0.001f),
|
||
"Spell damage has no collision modifiers and keeps its intermediate fraction.");
|
||
}
|
||
|
||
[Test]
|
||
public void CalculateIncomingDamage_PreservesFractionsUntilThePlayerHit()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"incoming-flat",
|
||
CharacterStat.IncomingDamage,
|
||
ModifierOperation.Flat,
|
||
0.8f));
|
||
stats.AddModifier(new StatModifier(
|
||
"incoming-increased",
|
||
CharacterStat.IncomingDamage,
|
||
ModifierOperation.Increased,
|
||
0.25f));
|
||
stats.AddModifier(new StatModifier(
|
||
"incoming-more",
|
||
CharacterStat.IncomingDamage,
|
||
ModifierOperation.More,
|
||
0.2f));
|
||
|
||
float incomingDamage = stats.CalculateIncomingDamage(10f);
|
||
Assert.That(incomingDamage, Is.EqualTo(16.2f).Within(0.001f));
|
||
Assert.That(
|
||
stats.Evaluate(CharacterStat.IncomingDamage, 10f),
|
||
Is.EqualTo(incomingDamage).Within(0.001f));
|
||
Assert.That(DamageCalculator.FinalizeDamage(incomingDamage), Is.EqualTo(16f));
|
||
}
|
||
|
||
[Test]
|
||
public void CalculateDamage_PreservesFractionsAcrossMoreStacks()
|
||
{
|
||
StatModifier more = new(
|
||
"damage-more-stacked",
|
||
CharacterStat.CollisionDamage,
|
||
ModifierOperation.More,
|
||
0.2f,
|
||
3);
|
||
stats.AddModifier(more);
|
||
stats.AddModifier(more);
|
||
stats.AddModifier(more);
|
||
|
||
float calculatedDamage = stats.CalculateDamage(10f, DamageTag.Collision);
|
||
Assert.That(calculatedDamage, Is.EqualTo(17.28f).Within(0.001f));
|
||
Assert.That(
|
||
DamageCalculator.FinalizeDamage(calculatedDamage),
|
||
Is.EqualTo(17),
|
||
"10 × 1.2 × 1.2 × 1.2 stays fractional until the final hit.");
|
||
}
|
||
|
||
[Test]
|
||
public void Evaluate_IncomingDamageMoreModifierReducesReceivedDamage()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"artifact.pulse.damage-reduction",
|
||
CharacterStat.IncomingDamage,
|
||
ModifierOperation.More,
|
||
-0.35f));
|
||
|
||
Assert.That(
|
||
stats.Evaluate(CharacterStat.IncomingDamage, 20f),
|
||
Is.EqualTo(13f).Within(0.001f));
|
||
}
|
||
|
||
[Test]
|
||
public void AddModifier_StopsAtMaxStacksAndReportsCurrentStacks()
|
||
{
|
||
StatModifier modifier = new(
|
||
"level-up.move-speed",
|
||
CharacterStat.MoveSpeed,
|
||
ModifierOperation.Increased,
|
||
0.05f,
|
||
2);
|
||
|
||
Assert.That(stats.AddModifier(modifier), Is.True);
|
||
Assert.That(stats.AddModifier(modifier), Is.True);
|
||
Assert.That(stats.AddModifier(modifier), Is.False);
|
||
Assert.That(
|
||
stats.GetStackCount("level-up.move-speed", CharacterStat.MoveSpeed),
|
||
Is.EqualTo(2));
|
||
|
||
AppliedStatModifier applied = stats.GetAppliedModifiers()[0];
|
||
Assert.That(applied.CurrentStacks, Is.EqualTo(2));
|
||
Assert.That(applied.MaxStacks, Is.EqualTo(2));
|
||
}
|
||
|
||
[Test]
|
||
public void RemoveModifiersFromSource_RemovesEveryStackFromThatSource()
|
||
{
|
||
StatModifier modifier = new(
|
||
"temporary-buff",
|
||
CharacterStat.ArtifactGaugeGain,
|
||
ModifierOperation.Increased,
|
||
0.1f,
|
||
3);
|
||
stats.AddModifier(modifier);
|
||
stats.AddModifier(modifier);
|
||
|
||
Assert.That(
|
||
stats.RemoveModifiersFromSource("temporary-buff"),
|
||
Is.EqualTo(2));
|
||
Assert.That(
|
||
stats.GetStackCount(
|
||
"temporary-buff",
|
||
CharacterStat.ArtifactGaugeGain),
|
||
Is.Zero);
|
||
Assert.That(
|
||
stats.Evaluate(CharacterStat.ArtifactGaugeGain, 5f),
|
||
Is.EqualTo(5f).Within(0.001f));
|
||
}
|
||
|
||
[Test]
|
||
public void BaseProperties_UseConfiguredDefaultsAndModifiers()
|
||
{
|
||
stats.AddModifier(new StatModifier(
|
||
"level-up.max-health",
|
||
CharacterStat.MaxHealth,
|
||
ModifierOperation.Flat,
|
||
10f));
|
||
stats.AddModifier(new StatModifier(
|
||
"level-up.move-speed",
|
||
CharacterStat.MoveSpeed,
|
||
ModifierOperation.Increased,
|
||
0.05f));
|
||
|
||
Assert.That(stats.MaxHealth, Is.EqualTo(110f).Within(0.001f));
|
||
Assert.That(stats.MoveSpeed, Is.EqualTo(3.15f).Within(0.001f));
|
||
}
|
||
|
||
[Test]
|
||
public void CharacterStat_KeepsExistingSerializedValuesAndAppendsAttackDamage()
|
||
{
|
||
Assert.That((int)CharacterStat.CollisionDamage, Is.EqualTo(0));
|
||
Assert.That((int)CharacterStat.IncomingDamage, Is.EqualTo(8));
|
||
Assert.That((int)CharacterStat.AttackDamage, Is.EqualTo(9));
|
||
}
|
||
}
|
||
}
|