306 lines
12 KiB
C#
306 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 IPokemon CreateFieldPokemon(byte side, byte position, bool onField = true)
|
|
{
|
|
var pokemon = Substitute.For<IPokemon>();
|
|
var battleData = Substitute.For<IPokemonBattleData>();
|
|
battleData.IsOnBattlefield.Returns(onField);
|
|
battleData.SideIndex.Returns(side);
|
|
battleData.Position.Returns(position);
|
|
pokemon.BattleData.Returns(battleData);
|
|
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, IPokemon 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, IPokemon 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<IPokemon>();
|
|
var battleData = Substitute.For<IPokemonBattleData>();
|
|
battleData.Battle.Returns(battle);
|
|
user.BattleData.Returns(battleData);
|
|
|
|
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: outside of battle (no battle data) the script cannot resolve a move to copy, so
|
|
/// the move name must remain unchanged.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_NoBattleData_MoveNameUnchanged()
|
|
{
|
|
// Arrange
|
|
var script = new MirrorMove();
|
|
var user = Substitute.For<IPokemon>();
|
|
user.BattleData.Returns((IPokemonBattleData?)null);
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
StringKey moveName = "mirror_move";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("mirror_move"));
|
|
}
|
|
|
|
/// <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"));
|
|
}
|
|
} |