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; /// /// Tests for the move script and its . /// 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." /// public class EndureTests { /// /// Creates a fully mocked setup for driving 's inherited /// . The target of the secondary effect is the /// Pokémon using Endure itself, as the move is self-targeted. /// 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(); var target = Substitute.For(); var hitData = Substitute.For(); 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()]); var random = Substitute.For(); random.GetFloat().Returns(randomRoll); var battle = Substitute.For(); battle.ChoiceQueue.Returns(queue); battle.Random.Returns(random); var battleData = Substitute.For(); battleData.Battle.Returns(battle); target.BattleData.Returns(battleData); target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty>())); IScriptSet volatileSet = new ScriptSet(target); target.Volatile.Returns(volatileSet); return (script, move, target, hitData, volatileSet); } /// /// 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 to the user. /// [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())).IsTrue(); hitData.DidNotReceive().Fail(); } /// /// 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. /// [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())).IsFalse(); } /// /// 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. /// [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())).IsFalse(); } /// /// 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. /// [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())).IsTrue(); hitData.DidNotReceive().Fail(); } /// /// 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. /// [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(); pokemon.CurrentHealth.Returns(currentHealth); var damage = incomingDamage; // Act effect.ChangeIncomingDamage(pokemon, DamageSource.MoveDamage, ref damage); // Assert await Assert.That(damage).IsEqualTo(expectedDamage); } /// /// 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. /// [Test] public async Task ChangeIncomingDamage_DamageEqualToCurrentHp_LeavesUserWithOneHp() { // Arrange var effect = new EndureEffect(); var pokemon = Substitute.For(); pokemon.CurrentHealth.Returns(100u); var damage = 100u; // Act effect.ChangeIncomingDamage(pokemon, DamageSource.MoveDamage, ref damage); // Assert await Assert.That(damage).IsEqualTo(99u); } /// /// Bulbapedia: Endure only affects attacks "that would cause it to faint" — non-lethal damage passes /// through unchanged. /// [Test] public async Task ChangeIncomingDamage_NonLethalDamage_DamageUnchanged() { // Arrange var effect = new EndureEffect(); var pokemon = Substitute.For(); pokemon.CurrentHealth.Returns(100u); var damage = 50u; // Act effect.ChangeIncomingDamage(pokemon, DamageSource.MoveDamage, ref damage); // Assert await Assert.That(damage).IsEqualTo(50u); } /// /// Bulbapedia: the protection lasts "for the rest of the turn" — the effect removes itself at the end /// of the turn. /// [Test] public async Task OnEndTurn_EffectRemovesItself() { // Arrange var owner = Substitute.For(); owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty>())); IScriptSet ownerVolatile = new ScriptSet(owner); var effect = new EndureEffect(); ownerVolatile.Add(effect); // Act effect.OnEndTurn(owner, Substitute.For()); // Assert await Assert.That(ownerVolatile.Contains(ScriptUtils.ResolveName())).IsFalse(); } }