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; /// /// Tests for the move script and its 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." /// public class MeFirstTests { /// /// Creates a substitute move choice for the target, queued with the given move name and category. /// private static IMoveChoice CreateTargetMoveChoice(IBattlePokemon target, string moveName, MoveCategory category = MoveCategory.Physical) { var moveData = Substitute.For(); moveData.Name.Returns(new StringKey(moveName)); moveData.Category.Returns(category); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); var moveChoice = Substitute.For(); moveChoice.User.Returns(target); moveChoice.ChosenMove.Returns(learnedMove); return moveChoice; } /// /// Creates a fully mocked test setup for Me First tests. The user's move choice targets side 0, /// position 0, where is standing (or nothing, when null). The battle's /// choice queue holds the given remaining choices for this turn. /// private static (MeFirst script, IMoveChoice choice) CreateTestSetup(IBattlePokemon? target, ITurnChoice[] queuedChoices, bool targetChoiceIsReplacement = false) { var script = new MeFirst(); var side = Substitute.For(); side.Pokemon.Returns(new List { target }); var miscLibrary = Substitute.For(); miscLibrary.IsReplacementChoice(Arg.Any()).Returns(targetChoiceIsReplacement); var library = Substitute.For(); library.MiscLibrary.Returns(miscLibrary); var battle = Substitute.For(); battle.Sides.Returns(new[] { side }); battle.ChoiceQueue.Returns(new BattleChoiceQueue(queuedChoices)); battle.Library.Returns(library); var user = Substitute.For(); user.Battle.Returns(battle); var choice = Substitute.For(); 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>())); return (script, choice); } /// /// 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. /// [Test] public async Task ChangeMove_TargetSelectedDamagingMove_ChangesMoveToTargetsMove() { // Arrange var target = Substitute.For(); 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(); } /// /// Bulbapedia: "Me First copies that move preemptively and increases the power by 50%." /// Copying the move attaches the volatile to the move choice, which is /// responsible for the power increase. /// [Test] public async Task ChangeMove_TargetSelectedDamagingMove_AddsPowerBoostVolatile() { // Arrange var target = Substitute.For(); 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())).IsTrue(); } /// /// Bulbapedia: "Me First copies that move preemptively and increases the power by 50%." /// The volatile multiplies the copied move's base power by 1.5, /// truncating fractions and capping at . /// [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(); var target = Substitute.For(); // Act boost.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(basePower).IsEqualTo(expected); } /// /// 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. /// [Test] public async Task ChangeMove_TargetAlreadyExecutedMove_Fails() { // Arrange var target = Substitute.For(); 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")); } /// /// 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." /// [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")); } /// /// 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. /// [Test] public async Task ChangeMove_TargetChoiceIsNotAMoveChoice_Fails() { // Arrange var target = Substitute.For(); var switchChoice = Substitute.For(); 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")); } /// /// 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. /// [Test] public async Task ChangeMove_TargetChoiceIsReplacementChoice_Fails() { // Arrange var target = Substitute.For(); 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")); } /// /// 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. /// [Test] public async Task ChangeMove_TargetSelectedNonDamagingMove_Fails() { // Arrange var target = Substitute.For(); 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")); } /// /// 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. /// [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(); 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")); } }