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"));
}
}