using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior (Generations V to VII): "Mirror Move now targets a specific Pokémon on /// the field, and copies the move most recently used by that Pokémon." The move fails if there is no /// move to copy, or if the move to copy is one that cannot be copied (such as Mirror Move itself). /// public class MirrorMoveTests { /// /// Creates a Pokémon on the battlefield at the given side and position. /// private static IBattlePokemon CreateFieldPokemon(byte side, byte position, bool onField = true) { var pokemon = Substitute.For(); pokemon.IsOnBattlefield.Returns(onField); pokemon.SideIndex.Returns(side); pokemon.Position.Returns(position); return pokemon; } /// /// Creates a move choice that was executed earlier in the battle: the given Pokémon used the given /// move against the given target side and position. /// private static IMoveChoice CreatePastMoveChoice(string moveName, IBattlePokemon user, byte targetSide, byte targetPosition) { var moveData = Substitute.For(); moveData.Name.Returns(new StringKey(moveName)); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); var choice = Substitute.For(); choice.ChosenMove.Returns(learnedMove); choice.User.Returns(user); choice.TargetSide.Returns(targetSide); choice.TargetPosition.Returns(targetPosition); return choice; } /// /// Creates the Mirror Move script together with a Mirror Move choice aimed at the given side and /// position, inside a mocked battle with an (initially empty) turn history and an empty choice queue. /// The targeted Pokémon is placed on the battlefield at that side and position and returned. /// private static (MirrorMove script, IMoveChoice choice, IBattle battle, IBattlePokemon target) CreateMirrorMoveSetup( byte targetSide, byte targetPosition) { var script = new MirrorMove(); var battle = Substitute.For(); battle.ChoiceQueue.Returns(new BattleChoiceQueue([])); SetTurnHistory(battle); var target = CreateFieldPokemon(targetSide, targetPosition); battle.GetPokemon(targetSide, targetPosition).Returns(target); var user = Substitute.For(); user.Battle.Returns(battle); var choice = Substitute.For(); choice.User.Returns(user); choice.TargetSide.Returns(targetSide); choice.TargetPosition.Returns(targetPosition); return (script, choice, battle, target); } /// /// Sets the battle's turn choice history to a single turn containing the given choices, in order. /// private static void SetTurnHistory(IBattle battle, params ITurnChoice[] choices) => battle.PreviousTurnChoices.Returns(new IReadOnlyList[] { choices }); /// /// Bulbapedia (Generations V to VII): "Mirror Move now targets a specific Pokémon on the field, and /// copies the move most recently used by that Pokémon." /// The targeted Pokémon (side 1, position 0) used Thunderbolt against the user, so Mirror Move must /// become Thunderbolt. /// [Test] public async Task ChangeMove_TargetUsedMoveOnUser_CopiesThatMove() { // Arrange - the target (side 1, position 0) used Thunderbolt against the user (side 0, position 0) var (script, choice, battle, target) = CreateMirrorMoveSetup(1, 0); var thunderbolt = CreatePastMoveChoice("thunderbolt", target, 0, 0); SetTurnHistory(battle, thunderbolt); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("thunderbolt")); choice.DidNotReceive().Fail(); } /// /// Bulbapedia (Generations V to VII): Mirror Move "copies the move most recently used by that /// Pokémon." The targeted Pokémon used a self-targeted move (Swords Dance), which Mirror Move copies. /// [Test] public async Task ChangeMove_TargetUsedSelfTargetedMove_CopiesThatMove() { // Arrange - the target (side 1, position 0) used Swords Dance on itself var (script, choice, battle, target) = CreateMirrorMoveSetup(1, 0); var swordsDance = CreatePastMoveChoice("swords_dance", target, 1, 0); SetTurnHistory(battle, swordsDance); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("swords_dance")); choice.DidNotReceive().Fail(); } /// /// Bulbapedia (Generations V to VII): Mirror Move "copies the move most recently used by that /// Pokémon" - with multiple candidate moves in the history, the most recent one is copied. /// [Test] public async Task ChangeMove_MultipleMovesInHistory_CopiesMostRecentOne() { // Arrange - the target used Growl first and Swords Dance afterwards var (script, choice, battle, target) = CreateMirrorMoveSetup(1, 0); var growl = CreatePastMoveChoice("growl", target, 1, 0); var swordsDance = CreatePastMoveChoice("swords_dance", target, 1, 0); SetTurnHistory(battle, growl, swordsDance); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("swords_dance")); choice.DidNotReceive().Fail(); } /// /// Bulbapedia (Generations V to VII): Mirror Move "copies the move most recently used by that /// Pokémon." A move that a different Pokémon aimed at the target was not used by the target, so with /// no move used by the target itself, Mirror Move fails. /// [Test] public async Task ChangeMove_MoveUsedByAnotherPokemonOnTarget_Fails() { // Arrange - an ally (side 0, position 1) used Tackle on the target (side 1, position 0); // the target itself has not used a move var (script, choice, battle, _) = CreateMirrorMoveSetup(1, 0); var ally = CreateFieldPokemon(0, 1); var tackle = CreatePastMoveChoice("tackle", ally, 1, 0); SetTurnHistory(battle, tackle); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.Received(1).Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("mirror_move")); } /// /// Bulbapedia: "The move fails if no moves were previously targeted at the user" (Generation I to IV) /// / there is no move used by the target to copy (Generations V to VII): with an empty turn history, /// Mirror Move fails. /// [Test] public async Task ChangeMove_NoPreviousMoves_Fails() { // Arrange var (script, choice, _, _) = CreateMirrorMoveSetup(1, 0); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.Received(1).Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("mirror_move")); } /// /// Bulbapedia: the move fails "if it would copy itself" - and in general if the move to copy is one /// that cannot be copied (such as Counter or Transform). /// [Test, Arguments("mirror_move"), Arguments("counter"), Arguments("transform")] public async Task ChangeMove_LastMoveCannotBeCopied_Fails(string lastMove) { // Arrange - the target used a non-copyable move on itself var (script, choice, battle, target) = CreateMirrorMoveSetup(1, 0); var pastChoice = CreatePastMoveChoice(lastMove, target, 1, 0); SetTurnHistory(battle, pastChoice); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.Received(1).Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("mirror_move")); } /// /// Bulbapedia: "Mirror Move uses the last move targeted at the user by a Pokémon still on the field." /// A move whose user has since left the battlefield is not considered, so Mirror Move fails. /// [Test] public async Task ChangeMove_MoveUserNoLongerOnField_Fails() { // Arrange - the Pokémon that used the move has switched out since; a replacement (the current // target at side 1, position 0) now stands in its spot var (script, choice, battle, _) = CreateMirrorMoveSetup(1, 0); var departed = CreateFieldPokemon(1, 0, false); var swordsDance = CreatePastMoveChoice("swords_dance", departed, 1, 0); SetTurnHistory(battle, swordsDance); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.Received(1).Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("mirror_move")); } /// /// Bulbapedia: Mirror Move copies the move most recently used - i.e. before Mirror Move itself /// executes. Choices that come after the currently executing Mirror Move choice in the turn history /// (they have not happened yet) must be ignored. /// [Test] public async Task ChangeMove_MovesAfterCurrentlyExecutingChoice_AreIgnored() { // Arrange - this turn: the target used Growl, then Mirror Move executes, then the target's // later Swords Dance choice is still pending in the history var (script, choice, battle, target) = CreateMirrorMoveSetup(1, 0); var growl = CreatePastMoveChoice("growl", target, 1, 0); var swordsDance = CreatePastMoveChoice("swords_dance", target, 1, 0); SetTurnHistory(battle, growl, choice, swordsDance); var queue = new BattleChoiceQueue([choice]); queue.Dequeue(); // Makes the Mirror Move choice the last ran choice battle.ChoiceQueue.Returns(queue); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert - only Growl was used before Mirror Move executed await Assert.That(moveName).IsEqualTo(new StringKey("growl")); choice.DidNotReceive().Fail(); } /// /// Technical test: without a running choice queue (no turn is being executed) the script cannot /// determine the current point in the turn, so the move name must remain unchanged. /// [Test] public async Task ChangeMove_NoChoiceQueue_MoveNameUnchanged() { // Arrange var (script, choice, battle, _) = CreateMirrorMoveSetup(1, 0); battle.ChoiceQueue.Returns((BattleChoiceQueue?)null); StringKey moveName = "mirror_move"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("mirror_move")); } }