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; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "The Assist user executes a move that is randomly selected from all the /// eligible moves known by other Pokémon in the same party. If the same move is known by multiple Pokémon, /// each of them counts [...]. Assist can call a move that currently has no PP or is known by a fainted /// Pokémon. [...] If the other Pokémon in the user's party do not know any eligible moves, Assist fails." /// public class AssistTests { private static IPokemon CreatePokemonWithMoves(params string[] moveNames) { var pokemon = Substitute.For(); var moves = moveNames.Select(name => { var learned = Substitute.For(); var data = Substitute.For(); data.Name.Returns(new StringKey(name)); learned.MoveData.Returns(data); return (ILearnedMove?)learned; }).ToArray(); pokemon.Moves.Returns(moves); return pokemon; } private static (Assist script, IMoveChoice choice, IPokemon user, IBattleRandom random) CreateTestSetup( params IPokemon?[] otherPartyMembers) { var script = new Assist(); var user = CreatePokemonWithMoves("tackle"); var members = new List { user }; members.AddRange(otherPartyMembers); var party = Substitute.For(); party.GetEnumerator().Returns(_ => members.GetEnumerator()); var battleParty = Substitute.For(); battleParty.Party.Returns(party); var random = Substitute.For(); var battle = Substitute.For(); battle.Parties.Returns(new[] { battleParty }); battle.Random.Returns(random); var battleData = Substitute.For(); battleData.Battle.Returns(battle); user.BattleData.Returns(battleData); var choice = Substitute.For(); choice.User.Returns(user); return (script, choice, user, random); } /// /// Bulbapedia: "The Assist user executes a move that is randomly selected from all the eligible moves known /// by other Pokémon in the same party." /// [Test] public async Task ChangeMove_AllyKnowsEligibleMove_SelectsThatMove() { // Arrange var (script, choice, _, random) = CreateTestSetup(CreatePokemonWithMoves("growl")); random.GetInt(Arg.Any()).Returns(0); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("growl")); choice.DidNotReceive().Fail(); } /// /// Bulbapedia: "If the other Pokémon in the user's party do not know any eligible moves, Assist fails." /// The user's own moves are not eligible: with no other party members, the move fails even though the /// user itself knows a copyable move. /// [Test] public async Task ChangeMove_NoOtherPartyMembers_Fails() { // Arrange var (script, choice, _, _) = CreateTestSetup(); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.Received(1).Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("assist")); } /// /// Bulbapedia lists moves that Assist cannot call (e.g. Protect, Counter, moves with a semi-invulnerable /// turn). An ally that only knows ineligible moves provides no options, so Assist fails. /// [Test] public async Task ChangeMove_AllyOnlyKnowsIneligibleMoves_Fails() { // Arrange var (script, choice, _, _) = CreateTestSetup(CreatePokemonWithMoves("protect", "counter", "dig")); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.Received(1).Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("assist")); } /// /// Bulbapedia: "If the same move is known by multiple Pokémon, each of them counts and therefore the move /// is more likely to be called." Two allies knowing Tackle plus one knowing Growl yields three entries in /// the random selection pool. /// [Test] public void ChangeMove_SameMoveKnownByMultipleAllies_EachCounts() { // Arrange var (script, choice, _, random) = CreateTestSetup(CreatePokemonWithMoves("pound"), CreatePokemonWithMoves("pound"), CreatePokemonWithMoves("growl")); random.GetInt(Arg.Any()).Returns(0); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert - the selection pool contains three moves (pound twice, growl once) random.Received(1).GetInt(3); choice.DidNotReceive().Fail(); } /// /// Bulbapedia: "Assist can call a move that [...] is known by a fainted Pokémon." /// [Test] public async Task ChangeMove_AllyIsFainted_MoveStillEligible() { // Arrange var faintedAlly = CreatePokemonWithMoves("growl"); faintedAlly.IsUsable.Returns(false); var (script, choice, _, random) = CreateTestSetup(faintedAlly); random.GetInt(Arg.Any()).Returns(0); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("growl")); choice.DidNotReceive().Fail(); } /// /// Bulbapedia: "Assist can call a move that currently has no PP". /// [Test] public async Task ChangeMove_AllyMoveHasNoPp_MoveStillEligible() { // Arrange var ally = CreatePokemonWithMoves("growl"); ally.Moves[0]!.CurrentPp.Returns((byte)0); var (script, choice, _, random) = CreateTestSetup(ally); random.GetInt(Arg.Any()).Returns(0); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("growl")); choice.DidNotReceive().Fail(); } /// /// Technical test: outside of battle (no battle data) the script returns without failing the choice. /// [Test] public async Task ChangeMove_NoBattleData_DoesNothing() { // Arrange var script = new Assist(); var user = Substitute.For(); user.BattleData.Returns((IPokemonBattleData?)null); var choice = Substitute.For(); choice.User.Returns(user); StringKey moveName = "assist"; // Act script.ChangeMove(choice, ref moveName); // Assert choice.DidNotReceive().Fail(); await Assert.That(moveName).IsEqualTo(new StringKey("assist")); } }