270 lines
11 KiB
C#
270 lines
11 KiB
C#
using PkmnLib.Dynamic.Libraries;
|
|
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.MoveVolatile;
|
|
using PkmnLib.Static.Moves;
|
|
using PkmnLib.Static.Utils;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="MeFirst"/> move script and its <see cref="MeFirstPowerBoost"/> volatile.
|
|
/// Gen VII Bulbapedia behavior: "If the target has not made its move this turn, but has selected a
|
|
/// damage-dealing move, Me First copies that move preemptively and increases the power by 50%."
|
|
/// "Me First will fail if the target selected a non-damaging move, or if the target already executed its
|
|
/// move this turn."
|
|
/// </summary>
|
|
public class MeFirstTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a substitute move choice for the target, queued with the given move name and category.
|
|
/// </summary>
|
|
private static IMoveChoice CreateTargetMoveChoice(IBattlePokemon target, string moveName,
|
|
MoveCategory category = MoveCategory.Physical)
|
|
{
|
|
var moveData = Substitute.For<IMoveData>();
|
|
moveData.Name.Returns(new StringKey(moveName));
|
|
moveData.Category.Returns(category);
|
|
var learnedMove = Substitute.For<ILearnedMove>();
|
|
learnedMove.MoveData.Returns(moveData);
|
|
var moveChoice = Substitute.For<IMoveChoice>();
|
|
moveChoice.User.Returns(target);
|
|
moveChoice.ChosenMove.Returns(learnedMove);
|
|
return moveChoice;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a fully mocked test setup for Me First tests. The user's move choice targets side 0,
|
|
/// position 0, where <paramref name="target"/> is standing (or nothing, when null). The battle's
|
|
/// choice queue holds the given remaining choices for this turn.
|
|
/// </summary>
|
|
private static (MeFirst script, IMoveChoice choice) CreateTestSetup(IBattlePokemon? target,
|
|
ITurnChoice[] queuedChoices, bool targetChoiceIsReplacement = false)
|
|
{
|
|
var script = new MeFirst();
|
|
|
|
var side = Substitute.For<IBattleSide>();
|
|
side.Pokemon.Returns(new List<IBattlePokemon?> { target });
|
|
|
|
var miscLibrary = Substitute.For<IMiscLibrary>();
|
|
miscLibrary.IsReplacementChoice(Arg.Any<ITurnChoice>()).Returns(targetChoiceIsReplacement);
|
|
var library = Substitute.For<IDynamicLibrary>();
|
|
library.MiscLibrary.Returns(miscLibrary);
|
|
|
|
var battle = Substitute.For<IBattle>();
|
|
battle.Sides.Returns(new[] { side });
|
|
battle.ChoiceQueue.Returns(new BattleChoiceQueue(queuedChoices));
|
|
battle.Library.Returns(library);
|
|
|
|
var user = Substitute.For<IBattlePokemon>();
|
|
|
|
user.Battle.Returns(battle);
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
choice.TargetSide.Returns((byte)0);
|
|
choice.TargetPosition.Returns((byte)0);
|
|
// Use a real script set so the volatile script added by Me First can be inspected afterwards.
|
|
choice.Volatile.Returns(new ScriptSet(choice));
|
|
choice.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
|
|
|
return (script, choice);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "If the target has not made its move this turn, but has selected a damage-dealing move,
|
|
/// Me First copies that move preemptively".
|
|
/// The executed move is replaced by the move the target has queued.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetSelectedDamagingMove_ChangesMoveToTargetsMove()
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var targetChoice = CreateTargetMoveChoice(target, "tackle");
|
|
var (script, choice) = CreateTestSetup(target, [targetChoice]);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("tackle"));
|
|
choice.DidNotReceive().Fail();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Me First copies that move preemptively and increases the power by 50%."
|
|
/// Copying the move attaches the <see cref="MeFirstPowerBoost"/> volatile to the move choice, which is
|
|
/// responsible for the power increase.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetSelectedDamagingMove_AddsPowerBoostVolatile()
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var targetChoice = CreateTargetMoveChoice(target, "tackle");
|
|
var (script, choice) = CreateTestSetup(target, [targetChoice]);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
await Assert.That(choice.Volatile.Contains(ScriptUtils.ResolveName<MeFirstPowerBoost>())).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Me First copies that move preemptively and increases the power by 50%."
|
|
/// The <see cref="MeFirstPowerBoost"/> volatile multiplies the copied move's base power by 1.5,
|
|
/// truncating fractions and capping at <see cref="ushort.MaxValue"/>.
|
|
/// </summary>
|
|
[Test, Arguments((ushort)100, (ushort)150), Arguments((ushort)60, (ushort)90), Arguments((ushort)5, (ushort)7),
|
|
Arguments((ushort)85, (ushort)127), Arguments(ushort.MaxValue, ushort.MaxValue)]
|
|
public async Task ChangeBasePower_MeFirstPowerBoost_IncreasesPowerByFiftyPercent(ushort basePower, ushort expected)
|
|
{
|
|
// Arrange
|
|
var boost = new MeFirstPowerBoost();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
|
|
// Act
|
|
boost.ChangeBasePower(move, target, 0, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo(expected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Me First will fail if the target selected a non-damaging move, or if the target already
|
|
/// executed its move this turn."
|
|
/// The target's choice is no longer in the queue, so the move fails.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetAlreadyExecutedMove_Fails()
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var (script, choice) = CreateTestSetup(target, []);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("me_first"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Unlike other moves, if the target of Me First is a vacant slot or a Pokémon that already
|
|
/// fainted before the Me First user's turn, Me First will fail instead of being redirected to an adjacent
|
|
/// non-fainted opponent."
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetSlotIsVacant_Fails()
|
|
{
|
|
// Arrange
|
|
var (script, choice) = CreateTestSetup(null, []);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("me_first"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Me First only copies a move the target "has selected". A target that chose to switch out
|
|
/// instead of using a move has no selected move to copy, so Me First fails.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetChoiceIsNotAMoveChoice_Fails()
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var switchChoice = Substitute.For<ISwitchChoice>();
|
|
switchChoice.User.Returns(target);
|
|
var (script, choice) = CreateTestSetup(target, [switchChoice]);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("me_first"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Me First will fail if the target chose a Z-Move or any of the following moves."
|
|
/// Struggle is one of those moves; a replacement choice (the forced choice used when a Pokémon cannot
|
|
/// select a move, usually Struggle) cannot be copied.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetChoiceIsReplacementChoice_Fails()
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var targetChoice = CreateTargetMoveChoice(target, "struggle");
|
|
var (script, choice) = CreateTestSetup(target, [targetChoice], true);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("me_first"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Me First will fail if the target selected a non-damaging move, or if the target already
|
|
/// executed its move this turn."
|
|
/// A queued status move must make Me First fail instead of being copied.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeMove_TargetSelectedNonDamagingMove_Fails()
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var targetChoice = CreateTargetMoveChoice(target, "swords_dance", MoveCategory.Status);
|
|
var (script, choice) = CreateTestSetup(target, [targetChoice]);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("me_first"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Me First will fail if the target chose a Z-Move or any of the following moves." In
|
|
/// Generation VII the listed moves are Beak Blast, Belch, Chatter, Counter, Covet, Focus Punch, Me First,
|
|
/// Metal Burst, Mirror Coat, Shell Trap, Struggle and Thief.
|
|
/// </summary>
|
|
[Test, Arguments("beak_blast"), Arguments("belch"), Arguments("chatter"), Arguments("counter"), Arguments("covet"),
|
|
Arguments("focus_punch"), Arguments("metal_burst"), Arguments("mirror_coat"), Arguments("shell_trap"),
|
|
Arguments("thief")]
|
|
public async Task ChangeMove_TargetSelectedUncopyableMove_Fails(string targetMove)
|
|
{
|
|
// Arrange
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
var targetChoice = CreateTargetMoveChoice(target, targetMove);
|
|
var (script, choice) = CreateTestSetup(target, [targetChoice]);
|
|
StringKey moveName = "me_first";
|
|
|
|
// Act
|
|
script.ChangeMove(choice, ref moveName);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
await Assert.That(moveName).IsEqualTo(new StringKey("me_first"));
|
|
}
|
|
} |