Initial commit: Tiny Tackle Heroes Unity project

This commit is contained in:
2026-09-15 17:37:42 +09:00
commit 591fa9a826
1623 changed files with 182420 additions and 0 deletions
@@ -0,0 +1,267 @@
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));
}
}
}