Initial commit: Tiny Tackle Heroes Unity project
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
using System.Collections;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using BumpCombat.Spawning;
|
||||
using BumpCombat.Core;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BumpCombat.PlayModeTests
|
||||
{
|
||||
public sealed class ArtifactBalanceBuffRegressionTests
|
||||
{
|
||||
private GameObject isolationOwner;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
RunManager.ForceProductionModeForTests = false;
|
||||
ArtifactRewardController.GrantCatalogForTests = true;
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (isolationOwner != null)
|
||||
{
|
||||
Object.Destroy(isolationOwner);
|
||||
}
|
||||
RunManager.ForceProductionModeForTests = false;
|
||||
ArtifactRewardController.GrantCatalogForTests = false;
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator EnemyCharacterModel_ModifiersDriveRuntimeAndRemainInstanceLocal()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||||
if (director != null)
|
||||
{
|
||||
director.enabled = false;
|
||||
}
|
||||
|
||||
EnemyController enemy = FindOrdinaryEnemy();
|
||||
if (enemy == null && director != null)
|
||||
{
|
||||
director.DebugSpawnImmediate(1);
|
||||
yield return null;
|
||||
enemy = FindOrdinaryEnemy();
|
||||
}
|
||||
|
||||
Assert.That(enemy, Is.Not.Null);
|
||||
Assert.That(enemy.IsDead, Is.False);
|
||||
Assert.That(enemy.IsEventEnemy, Is.False);
|
||||
Assert.That(enemy.IsGroggyTierGated, Is.False);
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
Assert.That(player, Is.Not.Null);
|
||||
PlayerStats playerStats = player.GetComponent<PlayerStats>();
|
||||
EnemyModel enemyModel = enemy.GetComponent<EnemyModel>();
|
||||
Assert.That(enemyModel, Is.Not.Null);
|
||||
|
||||
float initialHealth = enemy.CurrentHealth;
|
||||
float initialMaximumHealth = enemy.MaximumHealth;
|
||||
float initialMoveSpeed = enemy.MoveSpeed;
|
||||
float initialAttackDamage = enemy.AttackDamage;
|
||||
Assert.That(initialMaximumHealth, Is.GreaterThan(0f));
|
||||
Assert.That(initialHealth, Is.GreaterThan(2f));
|
||||
Assert.That(initialMoveSpeed, Is.GreaterThan(0f));
|
||||
Assert.That(initialAttackDamage, Is.GreaterThan(0f));
|
||||
|
||||
Assert.That(enemyModel.AddModifier(new StatModifier(
|
||||
"test.enemy.max-health",
|
||||
CharacterStat.MaxHealth,
|
||||
ModifierOperation.Increased,
|
||||
0.5f)), Is.True);
|
||||
Assert.That(enemy.MaximumHealth, Is.EqualTo(initialMaximumHealth * 1.5f).Within(0.001f));
|
||||
Assert.That(enemy.CurrentHealth, Is.EqualTo(initialHealth).Within(0.001f));
|
||||
|
||||
Assert.That(enemyModel.AddModifier(new StatModifier(
|
||||
"test.enemy.move-speed",
|
||||
CharacterStat.MoveSpeed,
|
||||
ModifierOperation.Increased,
|
||||
0.25f)), Is.True);
|
||||
Assert.That(enemy.MoveSpeed, Is.EqualTo(initialMoveSpeed * 1.25f).Within(0.001f));
|
||||
|
||||
Assert.That(enemyModel.AddModifier(new StatModifier(
|
||||
"test.enemy.attack-damage",
|
||||
CharacterStat.AttackDamage,
|
||||
ModifierOperation.Increased,
|
||||
0.25f)), Is.True);
|
||||
Assert.That(enemy.AttackDamage, Is.EqualTo(initialAttackDamage * 1.25f).Within(0.001f));
|
||||
|
||||
Assert.That(enemyModel.AddModifier(new StatModifier(
|
||||
"test.enemy.incoming-damage",
|
||||
CharacterStat.IncomingDamage,
|
||||
ModifierOperation.Increased,
|
||||
0.25f)), Is.True);
|
||||
Assert.That(enemy.ApplyShock(3f, 0.2f), Is.True);
|
||||
Assert.That(enemy.TryTakeDamage(1.4f, Vector2.zero, 0f, false), Is.True);
|
||||
Assert.That(enemy.LastAppliedDamage, Is.EqualTo(2f).Within(0.001f));
|
||||
Assert.That(enemy.CurrentHealth, Is.EqualTo(initialHealth - 2f).Within(0.001f));
|
||||
|
||||
isolationOwner = new GameObject("Second Enemy Model");
|
||||
EnemyModel secondEnemyModel = isolationOwner.AddComponent<EnemyModel>();
|
||||
secondEnemyModel.ConfigureDefinition(enemy.Definition);
|
||||
Assert.That(
|
||||
secondEnemyModel.GetStackCount(
|
||||
"test.enemy.move-speed",
|
||||
CharacterStat.MoveSpeed),
|
||||
Is.Zero);
|
||||
Assert.That(
|
||||
playerStats.GetStackCount(
|
||||
"test.enemy.move-speed",
|
||||
CharacterStat.MoveSpeed),
|
||||
Is.Zero);
|
||||
|
||||
typeof(EnemyController)
|
||||
.GetProperty(nameof(enemy.CurrentHealth))
|
||||
.GetSetMethod(true)
|
||||
.Invoke(enemy, new object[] { enemy.MaximumHealth });
|
||||
enemy.enabled = false;
|
||||
Assert.That(
|
||||
enemyModel.RemoveModifiersFromSource("test.enemy.max-health"),
|
||||
Is.EqualTo(1));
|
||||
Assert.That(enemy.MaximumHealth, Is.EqualTo(initialMaximumHealth).Within(0.001f));
|
||||
Assert.That(enemy.CurrentHealth, Is.GreaterThan(enemy.MaximumHealth));
|
||||
|
||||
enemy.enabled = true;
|
||||
Assert.That(enemy.CurrentHealth, Is.EqualTo(initialMaximumHealth).Within(0.001f));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator IgniteLethalTick_UsesSceneDeathLifecycleOnce()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||||
Assert.That(director, Is.Not.Null);
|
||||
director.enabled = false;
|
||||
|
||||
EnemyController enemy = Object.FindAnyObjectByType<EnemyController>();
|
||||
if (enemy == null)
|
||||
{
|
||||
director.DebugSpawnImmediate(1);
|
||||
yield return null;
|
||||
enemy = Object.FindAnyObjectByType<EnemyController>();
|
||||
}
|
||||
|
||||
Assert.That(enemy, Is.Not.Null);
|
||||
int deathCount = 0;
|
||||
bool effectsClearedAtDeath = false;
|
||||
enemy.OnDied += deadEnemy =>
|
||||
{
|
||||
deathCount++;
|
||||
effectsClearedAtDeath = deadEnemy.IsDead
|
||||
&& !deadEnemy.IsIgnited
|
||||
&& !deadEnemy.IsShocked
|
||||
&& !deadEnemy.IsBumpVulnerable;
|
||||
};
|
||||
|
||||
Assert.That(enemy.ApplyShock(3f, 0.2f), Is.True);
|
||||
Assert.That(enemy.ApplyBumpVulnerability(3f, 0.25f), Is.True);
|
||||
Assert.That(
|
||||
enemy.ApplyIgnite(3f, 0.5f, enemy.CurrentHealth),
|
||||
Is.True);
|
||||
|
||||
float timeout = 1.5f;
|
||||
while (deathCount == 0 && timeout > 0f)
|
||||
{
|
||||
timeout -= Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.That(deathCount, Is.EqualTo(1));
|
||||
Assert.That(effectsClearedAtDeath, Is.True);
|
||||
|
||||
float despawnDeadline = Time.realtimeSinceStartup + 3f;
|
||||
while (enemy != null
|
||||
&& Time.realtimeSinceStartup < despawnDeadline)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.That(enemy == null, Is.True);
|
||||
Assert.That(deathCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator PulseAndCycloneBuffs_SurviveSelectionChangesWithExactValues()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
ActiveArtifactController artifacts =
|
||||
player.GetComponent<ActiveArtifactController>();
|
||||
PlayerStats stats = player.GetComponent<PlayerStats>();
|
||||
|
||||
SelectArtifact(artifacts, ActiveArtifactEffect.Pulse);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(
|
||||
stats.Evaluate(CharacterStat.IncomingDamage, 20f),
|
||||
Is.EqualTo(16f).Within(0.001f));
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"pulse.damage-reduction",
|
||||
CharacterStat.IncomingDamage),
|
||||
Is.EqualTo(1));
|
||||
Assert.That(artifacts.SelectNext(), Is.True);
|
||||
Assert.That(
|
||||
stats.Evaluate(CharacterStat.IncomingDamage, 20f),
|
||||
Is.EqualTo(16f).Within(0.001f));
|
||||
|
||||
SelectArtifact(artifacts, ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(artifacts.HasCycloneMoveSpeedBuff, Is.True);
|
||||
Assert.That(
|
||||
stats.MoveSpeed,
|
||||
Is.EqualTo(3.45f).Within(0.001f));
|
||||
|
||||
yield return new WaitForSecondsRealtime(0.45f);
|
||||
Assert.That(artifacts.SelectNext(), Is.True);
|
||||
Assert.That(artifacts.HasCycloneMoveSpeedBuff, Is.True);
|
||||
Assert.That(
|
||||
stats.MoveSpeed,
|
||||
Is.EqualTo(3.45f).Within(0.001f));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator CycloneBuff_AddsToGrowthAndPreservesGrowthAfterExpiry()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
ActiveArtifactController artifacts =
|
||||
player.GetComponent<ActiveArtifactController>();
|
||||
PlayerStats stats = player.GetComponent<PlayerStats>();
|
||||
Assert.That(
|
||||
stats.AddModifier(new StatModifier(
|
||||
"growth.move-speed",
|
||||
CharacterStat.MoveSpeed,
|
||||
ModifierOperation.Increased,
|
||||
0.1f)),
|
||||
Is.True);
|
||||
|
||||
SelectArtifact(artifacts, ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(stats.MoveSpeed, Is.EqualTo(3.75f).Within(0.001f));
|
||||
|
||||
yield return new WaitForSecondsRealtime(3.15f);
|
||||
|
||||
Assert.That(stats.MoveSpeed, Is.EqualTo(3.3f).Within(0.001f));
|
||||
Assert.That(
|
||||
stats.GetStackCount("growth.move-speed", CharacterStat.MoveSpeed),
|
||||
Is.EqualTo(1));
|
||||
Assert.That(
|
||||
stats.GetStackCount("cyclone.move-speed", CharacterStat.MoveSpeed),
|
||||
Is.Zero);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator CycloneBuff_StrongReapplicationWinsOverWeakReapplication()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
ActiveArtifactController artifacts =
|
||||
player.GetComponent<ActiveArtifactController>();
|
||||
PlayerStats stats = player.GetComponent<PlayerStats>();
|
||||
SelectArtifact(artifacts, ActiveArtifactEffect.Cyclone);
|
||||
|
||||
Assert.That(artifacts.TryUseCurrent(true), Is.True);
|
||||
yield return new WaitForSecondsRealtime(0.75f);
|
||||
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(stats.MoveSpeed, Is.EqualTo(3.75f).Within(0.001f));
|
||||
|
||||
yield return new WaitForSecondsRealtime(3.15f);
|
||||
Assert.That(
|
||||
stats.MoveSpeed,
|
||||
Is.EqualTo(3.75f).Within(0.001f),
|
||||
"A weak reapplication must not shorten the strong buff.");
|
||||
|
||||
yield return new WaitForSecondsRealtime(1.05f);
|
||||
Assert.That(stats.MoveSpeed, Is.EqualTo(3f).Within(0.001f));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TemporaryBuffs_PauseTimersAndClearOnControllerDisable()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
ActiveArtifactController artifacts =
|
||||
player.GetComponent<ActiveArtifactController>();
|
||||
PlayerStats stats = player.GetComponent<PlayerStats>();
|
||||
SelectArtifact(artifacts, ActiveArtifactEffect.Pulse);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"pulse.damage-reduction",
|
||||
CharacterStat.IncomingDamage),
|
||||
Is.EqualTo(1));
|
||||
|
||||
Time.timeScale = 0f;
|
||||
yield return new WaitForSecondsRealtime(0.35f);
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"pulse.damage-reduction",
|
||||
CharacterStat.IncomingDamage),
|
||||
Is.EqualTo(1));
|
||||
Assert.That(
|
||||
stats.Evaluate(CharacterStat.IncomingDamage, 20f),
|
||||
Is.EqualTo(16f).Within(0.001f));
|
||||
|
||||
artifacts.enabled = false;
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"pulse.damage-reduction",
|
||||
CharacterStat.IncomingDamage),
|
||||
Is.Zero);
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator CycloneBuff_CancelledByDamageStillExpiresAndPreservesGrowth()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
PlayerHealth health = player.GetComponent<PlayerHealth>();
|
||||
ActiveArtifactController artifacts =
|
||||
player.GetComponent<ActiveArtifactController>();
|
||||
PlayerStats stats = player.GetComponent<PlayerStats>();
|
||||
Assert.That(
|
||||
stats.AddModifier(new StatModifier(
|
||||
"growth.move-speed.cancel-test",
|
||||
CharacterStat.MoveSpeed,
|
||||
ModifierOperation.Increased,
|
||||
0.1f)),
|
||||
Is.True);
|
||||
|
||||
SelectArtifact(artifacts, ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(artifacts.IsExecutingArtifact, Is.True);
|
||||
Assert.That(health.TryTakeDamage(1f, Vector2.zero, 0f), Is.True);
|
||||
|
||||
for (int frame = 0;
|
||||
frame < 30 && artifacts.IsExecutingArtifact;
|
||||
frame++)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
||||
Time.timeScale = 1f;
|
||||
yield return new WaitForSecondsRealtime(3.2f);
|
||||
|
||||
Assert.That(
|
||||
stats.MoveSpeed,
|
||||
Is.EqualTo(3.3f).Within(0.001f),
|
||||
"Cancelling CY must not orphan its three-second expiration.");
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"growth.move-speed.cancel-test",
|
||||
CharacterStat.MoveSpeed),
|
||||
Is.EqualTo(1));
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"cyclone.move-speed",
|
||||
CharacterStat.MoveSpeed),
|
||||
Is.Zero);
|
||||
}
|
||||
|
||||
private static IEnumerator LoadCombatPrototype()
|
||||
{
|
||||
AsyncOperation load =
|
||||
SceneManager.LoadSceneAsync("CombatPrototype");
|
||||
while (!load.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private static EnemyController FindOrdinaryEnemy()
|
||||
{
|
||||
EnemyController[] enemies =
|
||||
Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None);
|
||||
foreach (EnemyController enemy in enemies)
|
||||
{
|
||||
if (enemy != null
|
||||
&& !enemy.IsDead
|
||||
&& !enemy.IsEventEnemy
|
||||
&& !enemy.IsGroggyTierGated)
|
||||
{
|
||||
return enemy;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SelectArtifact(
|
||||
ActiveArtifactController artifacts,
|
||||
ActiveArtifactEffect effect)
|
||||
{
|
||||
Assert.That(artifacts.OwnedArtifactCount, Is.GreaterThanOrEqualTo(2));
|
||||
for (int i = 0; i < artifacts.OwnedArtifactCount; i++)
|
||||
{
|
||||
if (artifacts.CurrentArtifact != null
|
||||
&& artifacts.CurrentArtifact.Effect == effect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.That(artifacts.SelectNext(), Is.True);
|
||||
}
|
||||
|
||||
Assert.Fail($"Artifact {effect} was not found in the debug catalog.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user