237 lines
8.6 KiB
C#
237 lines
8.6 KiB
C#
using PkmnLib.Dynamic.Models;
|
|
using PkmnLib.Dynamic.Models.Choices;
|
|
using PkmnLib.Dynamic.ScriptHandling;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Moves;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="Ingrain"/> move script and its <see cref="IngrainEffect"/> volatile.
|
|
/// Gen VII Bulbapedia behavior: the user plants roots, restoring 1/16th of its maximum HP each turn while
|
|
/// becoming unable to switch out; forced-switch moves like Roar fail against it. From Generation IV onwards
|
|
/// the move grounds the affected Pokémon, making them susceptible to Ground moves even if Flying-type.
|
|
/// Generation VI onwards: "Ghost-type Pokémon under the effects of Ingrain are now able to switch out."
|
|
/// </summary>
|
|
public class IngrainTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a rooted Pokémon substitute with the given maximum HP, together with the
|
|
/// <see cref="IngrainEffect"/> that roots it.
|
|
/// </summary>
|
|
private static (IngrainEffect effect, IBattlePokemon owner) CreateRootedPokemon(uint maxHealth = 100)
|
|
{
|
|
var owner = Substitute.For<IBattlePokemon>();
|
|
owner.BoostedStats.Returns(new StatisticSet<uint>(maxHealth, 1, 1, 1, 1, 1));
|
|
return (new IngrainEffect(owner), owner);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to extract the heal amount from a Pokémon's received Heal calls.
|
|
/// </summary>
|
|
private static uint? GetHealAmount(IBattlePokemon pokemon)
|
|
{
|
|
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal");
|
|
return call != null ? (uint)call.GetArguments()[0]! : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "The user plants roots" — using the move attaches the <see cref="IngrainEffect"/>
|
|
/// volatile to the user.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_Used_AddsIngrainEffectToUser()
|
|
{
|
|
// Arrange
|
|
var script = new Ingrain();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
var user = Substitute.For<IBattlePokemon>();
|
|
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
|
var userVolatile = new ScriptSet(user);
|
|
user.Volatile.Returns(userVolatile);
|
|
move.User.Returns(user);
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(userVolatile.Get<IngrainEffect>()).IsNotNull();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the roots restore "1/16th maximum HP each turn".
|
|
/// </summary>
|
|
[Test, Arguments(160u, 10u), Arguments(100u, 6u), Arguments(17u, 1u)]
|
|
public async Task OnEndTurn_RootedPokemon_HealsOneSixteenthOfMaxHp(uint maxHealth, uint expectedHeal)
|
|
{
|
|
// Arrange
|
|
var (effect, owner) = CreateRootedPokemon(maxHealth);
|
|
|
|
// Act
|
|
effect.OnEndTurn(owner, Substitute.For<IBattle>());
|
|
|
|
// Assert
|
|
await Assert.That(GetHealAmount(owner)!.Value).IsEqualTo(expectedHeal);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the rooted user becomes "unable to switch out".
|
|
/// </summary>
|
|
[Test]
|
|
public async Task PreventSelfSwitch_RootedPokemon_CannotSwitchOut()
|
|
{
|
|
// Arrange
|
|
var (effect, _) = CreateRootedPokemon();
|
|
var prevent = false;
|
|
|
|
// Act
|
|
effect.PreventSelfSwitch(Substitute.For<ISwitchChoice>(), ref prevent);
|
|
|
|
// Assert
|
|
await Assert.That(prevent).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the user is rooted in place; a rooted wild Pokémon cannot flee the battle.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task PreventSelfRunAway_RootedPokemon_CannotFlee()
|
|
{
|
|
// Arrange
|
|
var (effect, _) = CreateRootedPokemon();
|
|
var prevent = false;
|
|
|
|
// Act
|
|
effect.PreventSelfRunAway(Substitute.For<IFleeChoice>(), ref prevent);
|
|
|
|
// Assert
|
|
await Assert.That(prevent).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the rooted user cannot be forced out by "forced-switch moves like Roar" — Roar and
|
|
/// Whirlwind fail against it.
|
|
/// </summary>
|
|
[Test, Arguments("roar"), Arguments("whirlwind")]
|
|
public async Task FailIncomingMove_ForcedSwitchMove_MoveFails(string moveName)
|
|
{
|
|
// Arrange
|
|
var (effect, owner) = CreateRootedPokemon();
|
|
var incomingMove = Substitute.For<IExecutingMove>();
|
|
var moveData = Substitute.For<IMoveData>();
|
|
moveData.Name.Returns(new Static.Utils.StringKey(moveName));
|
|
incomingMove.UseMove.Returns(moveData);
|
|
var fail = false;
|
|
|
|
// Act
|
|
effect.FailIncomingMove(incomingMove, owner, ref fail);
|
|
|
|
// Assert
|
|
await Assert.That(fail).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: only forced-switch moves are blocked by the roots; any other incoming move works
|
|
/// normally.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task FailIncomingMove_RegularMove_MoveDoesNotFail()
|
|
{
|
|
// Arrange
|
|
var (effect, owner) = CreateRootedPokemon();
|
|
var incomingMove = Substitute.For<IExecutingMove>();
|
|
var moveData = Substitute.For<IMoveData>();
|
|
moveData.Name.Returns(new Static.Utils.StringKey("tackle"));
|
|
incomingMove.UseMove.Returns(moveData);
|
|
var fail = false;
|
|
|
|
// Act
|
|
effect.FailIncomingMove(incomingMove, owner, ref fail);
|
|
|
|
// Assert
|
|
await Assert.That(fail).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia (Generation IV onwards): the move grounds the affected Pokémon, making them susceptible to
|
|
/// Ground moves even if Flying-type. For an incoming Ground-type move, the rooted Pokémon's
|
|
/// Ground-immune types are ignored.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeTypesForIncomingMove_GroundMoveAgainstFlyingType_RemovesGroundImmuneType()
|
|
{
|
|
// Arrange
|
|
var (effect, owner) = CreateRootedPokemon();
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
owner.Library.Returns(library);
|
|
var types = library.StaticLibrary.Types;
|
|
await Assert.That(types.TryGetTypeIdentifier("ground", out var ground)).IsTrue();
|
|
await Assert.That(types.TryGetTypeIdentifier("flying", out var flying)).IsTrue();
|
|
await Assert.That(types.TryGetTypeIdentifier("grass", out var grass)).IsTrue();
|
|
|
|
var incomingMove = Substitute.For<IExecutingMove>();
|
|
var moveData = Substitute.For<IMoveData>();
|
|
moveData.MoveType.Returns(ground);
|
|
incomingMove.UseMove.Returns(moveData);
|
|
var targetTypes = new List<TypeIdentifier> { flying, grass };
|
|
|
|
// Act
|
|
effect.ChangeTypesForIncomingMove(incomingMove, owner, 0, targetTypes);
|
|
|
|
// Assert
|
|
await Assert.That(targetTypes.Contains(flying)).IsFalse();
|
|
await Assert.That(targetTypes.Contains(grass)).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The grounding only matters for Ground-type moves: any other incoming move sees the rooted Pokémon's
|
|
/// types unchanged.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeTypesForIncomingMove_NonGroundMove_TypesUnchanged()
|
|
{
|
|
// Arrange
|
|
var (effect, owner) = CreateRootedPokemon();
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
owner.Library.Returns(library);
|
|
var types = library.StaticLibrary.Types;
|
|
await Assert.That(types.TryGetTypeIdentifier("water", out var water)).IsTrue();
|
|
await Assert.That(types.TryGetTypeIdentifier("flying", out var flying)).IsTrue();
|
|
|
|
var incomingMove = Substitute.For<IExecutingMove>();
|
|
var moveData = Substitute.For<IMoveData>();
|
|
moveData.MoveType.Returns(water);
|
|
incomingMove.UseMove.Returns(moveData);
|
|
var targetTypes = new List<TypeIdentifier> { flying };
|
|
|
|
// Act
|
|
effect.ChangeTypesForIncomingMove(incomingMove, owner, 0, targetTypes);
|
|
|
|
// Assert
|
|
await Assert.That(targetTypes.Contains(flying)).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia (Generation VI onwards): "Ghost-type Pokémon under the effects of Ingrain are now able to
|
|
/// switch out."
|
|
/// </summary>
|
|
[Test]
|
|
public async Task PreventSelfSwitch_GhostTypeRooted_CanStillSwitchOut()
|
|
{
|
|
// Arrange
|
|
var (effect, owner) = CreateRootedPokemon();
|
|
owner.Types.Returns(new List<TypeIdentifier> { new(8, "ghost") });
|
|
var prevent = false;
|
|
var choice = Substitute.For<ISwitchChoice>();
|
|
choice.User.Returns(owner);
|
|
|
|
// Act
|
|
effect.PreventSelfSwitch(choice, ref prevent);
|
|
|
|
// Assert
|
|
await Assert.That(prevent).IsFalse();
|
|
}
|
|
} |