Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/EndureTests.cs
2026-07-05 19:45:40 +02:00

208 lines
7.9 KiB
C#

using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="Endure"/> move script and its <see cref="EndureEffect"/>.
/// Gen VII Bulbapedia behavior: "For the rest of the turn, Endure allows the user to survive any attack
/// that would cause it to faint, leaving the user with 1 HP instead." Like other protection moves, "if it
/// or other protection moves are used consecutively, its success rate decreases."
/// </summary>
public class EndureTests
{
/// <summary>
/// Creates a fully mocked setup for driving <see cref="Endure"/>'s inherited
/// <see cref="ProtectionScript.OnSecondaryEffect"/>. The <c>target</c> of the secondary effect is the
/// Pokémon using Endure itself, as the move is self-targeted.
/// </summary>
private static (Endure script, IExecutingMove move, IPokemon target, IHitData hitData, IScriptSet volatileSet)
CreateProtectSetup(bool userMovesLast, float randomRoll)
{
var script = new Endure();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
// A queue with a remaining choice means another Pokémon still has to move after the user.
var queue = userMovesLast ? new BattleChoiceQueue([]) : new BattleChoiceQueue([Substitute.For<IMoveChoice>()]);
var random = Substitute.For<IBattleRandom>();
random.GetFloat().Returns(randomRoll);
var battle = Substitute.For<IBattle>();
battle.ChoiceQueue.Returns(queue);
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet volatileSet = new ScriptSet(target);
target.Volatile.Returns(volatileSet);
return (script, move, target, hitData, volatileSet);
}
/// <summary>
/// Bulbapedia: "For the rest of the turn, Endure allows the user to survive any attack that would
/// cause it to faint" — using the move attaches the <see cref="EndureEffect"/> to the user.
/// </summary>
[Test]
public async Task OnSecondaryEffect_FirstUse_AddsEndureEffect()
{
// Arrange
var (script, move, target, hitData, volatileSet) = CreateProtectSetup(false, 0.5f);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<EndureEffect>())).IsTrue();
hitData.DidNotReceive().Fail();
}
/// <summary>
/// Bulbapedia: Endure "always fails if the user is the last to act in the turn" — with no remaining
/// choices in the queue there is nothing left to protect against, and the move fails.
/// </summary>
[Test]
public async Task OnSecondaryEffect_UserMovesLast_FailsHit()
{
// Arrange
var (script, move, target, hitData, volatileSet) = CreateProtectSetup(true, 0.0f);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
hitData.Received(1).Fail();
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<EndureEffect>())).IsFalse();
}
/// <summary>
/// Bulbapedia: "If it or other protection moves are used consecutively, its success rate decreases."
/// On the second consecutive use the success chance is 1/3, so a roll above that fails.
/// </summary>
[Test]
public async Task OnSecondaryEffect_SecondConsecutiveUseWithHighRoll_FailsHit()
{
// Arrange
var (script, move, target, hitData, volatileSet) = CreateProtectSetup(false, 0.5f);
volatileSet.Add(new ProtectionFailureScript { ProtectTurns = 1 });
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
hitData.Received(1).Fail();
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<EndureEffect>())).IsFalse();
}
/// <summary>
/// Bulbapedia: "If it or other protection moves are used consecutively, its success rate decreases."
/// On the second consecutive use the success chance is 1/3, so a roll below that still succeeds.
/// </summary>
[Test]
public async Task OnSecondaryEffect_SecondConsecutiveUseWithLowRoll_AddsEndureEffect()
{
// Arrange
var (script, move, target, hitData, volatileSet) = CreateProtectSetup(false, 0.2f);
volatileSet.Add(new ProtectionFailureScript { ProtectTurns = 1 });
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<EndureEffect>())).IsTrue();
hitData.DidNotReceive().Fail();
}
/// <summary>
/// Bulbapedia: "Endure allows the user to survive any attack that would cause it to faint, leaving
/// the user with 1 HP instead." Damage exceeding the user's current HP is reduced to leave 1 HP.
/// </summary>
[Test, Arguments(100u, 150u, 99u), Arguments(50u, 51u, 49u), Arguments(2u, 9999u, 1u)]
public async Task ChangeIncomingDamage_LethalDamage_LeavesUserWithOneHp(uint currentHealth, uint incomingDamage,
uint expectedDamage)
{
// Arrange
var effect = new EndureEffect();
var pokemon = Substitute.For<IPokemon>();
pokemon.CurrentHealth.Returns(currentHealth);
var damage = incomingDamage;
// Act
effect.ChangeIncomingDamage(pokemon, DamageSource.MoveDamage, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(expectedDamage);
}
/// <summary>
/// Bulbapedia: Endure lets the user "survive any attack that would cause it to faint" — a hit for
/// exactly the user's current HP would faint it, so it too must be reduced to leave 1 HP.
/// </summary>
[Test]
public async Task ChangeIncomingDamage_DamageEqualToCurrentHp_LeavesUserWithOneHp()
{
// Arrange
var effect = new EndureEffect();
var pokemon = Substitute.For<IPokemon>();
pokemon.CurrentHealth.Returns(100u);
var damage = 100u;
// Act
effect.ChangeIncomingDamage(pokemon, DamageSource.MoveDamage, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(99u);
}
/// <summary>
/// Bulbapedia: Endure only affects attacks "that would cause it to faint" — non-lethal damage passes
/// through unchanged.
/// </summary>
[Test]
public async Task ChangeIncomingDamage_NonLethalDamage_DamageUnchanged()
{
// Arrange
var effect = new EndureEffect();
var pokemon = Substitute.For<IPokemon>();
pokemon.CurrentHealth.Returns(100u);
var damage = 50u;
// Act
effect.ChangeIncomingDamage(pokemon, DamageSource.MoveDamage, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(50u);
}
/// <summary>
/// Bulbapedia: the protection lasts "for the rest of the turn" — the effect removes itself at the end
/// of the turn.
/// </summary>
[Test]
public async Task OnEndTurn_EffectRemovesItself()
{
// Arrange
var owner = Substitute.For<IPokemon>();
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet ownerVolatile = new ScriptSet(owner);
var effect = new EndureEffect();
ownerVolatile.Add(effect);
// Act
effect.OnEndTurn(owner, Substitute.For<IBattle>());
// Assert
await Assert.That(ownerVolatile.Contains(ScriptUtils.ResolveName<EndureEffect>())).IsFalse();
}
}