Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MirrorMoveTests.cs

280 lines
12 KiB
C#

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;
/// <summary>
/// Tests for the <see cref="MirrorMove"/> 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).
/// </summary>
public class MirrorMoveTests
{
/// <summary>
/// Creates a Pokémon on the battlefield at the given side and position.
/// </summary>
private static IBattlePokemon CreateFieldPokemon(byte side, byte position, bool onField = true)
{
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.IsOnBattlefield.Returns(onField);
pokemon.SideIndex.Returns(side);
pokemon.Position.Returns(position);
return pokemon;
}
/// <summary>
/// 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.
/// </summary>
private static IMoveChoice CreatePastMoveChoice(string moveName, IBattlePokemon user, byte targetSide,
byte targetPosition)
{
var moveData = Substitute.For<IMoveData>();
moveData.Name.Returns(new StringKey(moveName));
var learnedMove = Substitute.For<ILearnedMove>();
learnedMove.MoveData.Returns(moveData);
var choice = Substitute.For<IMoveChoice>();
choice.ChosenMove.Returns(learnedMove);
choice.User.Returns(user);
choice.TargetSide.Returns(targetSide);
choice.TargetPosition.Returns(targetPosition);
return choice;
}
/// <summary>
/// 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.
/// </summary>
private static (MirrorMove script, IMoveChoice choice, IBattle battle, IBattlePokemon target) CreateMirrorMoveSetup(
byte targetSide, byte targetPosition)
{
var script = new MirrorMove();
var battle = Substitute.For<IBattle>();
battle.ChoiceQueue.Returns(new BattleChoiceQueue([]));
SetTurnHistory(battle);
var target = CreateFieldPokemon(targetSide, targetPosition);
battle.GetPokemon(targetSide, targetPosition).Returns(target);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
choice.TargetSide.Returns(targetSide);
choice.TargetPosition.Returns(targetPosition);
return (script, choice, battle, target);
}
/// <summary>
/// Sets the battle's turn choice history to a single turn containing the given choices, in order.
/// </summary>
private static void SetTurnHistory(IBattle battle, params ITurnChoice[] choices) =>
battle.PreviousTurnChoices.Returns(new IReadOnlyList<ITurnChoice>[] { choices });
/// <summary>
/// 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.
/// </summary>
[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();
}
/// <summary>
/// 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.
/// </summary>
[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();
}
/// <summary>
/// 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.
/// </summary>
[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();
}
/// <summary>
/// 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.
/// </summary>
[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"));
}
/// <summary>
/// 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.
/// </summary>
[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"));
}
/// <summary>
/// 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).
/// </summary>
[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"));
}
/// <summary>
/// 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.
/// </summary>
[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"));
}
/// <summary>
/// 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.
/// </summary>
[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();
}
/// <summary>
/// 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.
/// </summary>
[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"));
}
}