This commit is contained in:
294
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MeFirstTests.cs
Normal file
294
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/MeFirstTests.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
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(IPokemon 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(IPokemon? target, ITurnChoice[] queuedChoices,
|
||||
bool targetChoiceIsReplacement = false)
|
||||
{
|
||||
var script = new MeFirst();
|
||||
|
||||
var side = Substitute.For<IBattleSide>();
|
||||
side.Pokemon.Returns(new List<IPokemon?> { 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 battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.Battle.Returns(battle);
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.BattleData.Returns(battleData);
|
||||
|
||||
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<IPokemon>();
|
||||
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<IPokemon>();
|
||||
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<IPokemon>();
|
||||
|
||||
// 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<IPokemon>();
|
||||
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<IPokemon>();
|
||||
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<IPokemon>();
|
||||
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>
|
||||
/// Technical test: outside of battle (no battle data) the script returns without throwing and without
|
||||
/// changing the move.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeMove_NoBattleData_DoesNotChangeMove()
|
||||
{
|
||||
// Arrange
|
||||
var script = new MeFirst();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.BattleData.Returns((IPokemonBattleData?)null);
|
||||
var choice = Substitute.For<IMoveChoice>();
|
||||
choice.User.Returns(user);
|
||||
StringKey moveName = "me_first";
|
||||
|
||||
// Act
|
||||
script.ChangeMove(choice, ref moveName);
|
||||
|
||||
// Assert
|
||||
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<IPokemon>();
|
||||
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<IPokemon>();
|
||||
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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user