230 lines
9.3 KiB
C#
230 lines
9.3 KiB
C#
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.Common;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
|
|
using PkmnLib.Static.Moves;
|
|
using PkmnLib.Static.Utils;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="Encore"/> move script and its <see cref="EncoreEffect"/>.
|
|
/// Gen VII Bulbapedia behavior: "Encore temporarily prevents the target from using any move except the
|
|
/// last used move." It fails if the target hasn't used a move yet or its last move was Struggle, and
|
|
/// from Generation V onward the duration is "exactly 3 turns".
|
|
/// </summary>
|
|
public class EncoreTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a mocked move choice whose chosen move has the given name, optionally carrying the
|
|
/// <see cref="MoveFlags.CantRepeat"/> flag.
|
|
/// </summary>
|
|
private static IMoveChoice CreateMoveChoice(string moveName, bool cantRepeat = false)
|
|
{
|
|
var moveData = Substitute.For<IMoveData>();
|
|
moveData.Name.Returns(new StringKey(moveName));
|
|
moveData.HasFlag(MoveFlags.CantRepeat).Returns(cantRepeat);
|
|
var learnedMove = Substitute.For<ILearnedMove>();
|
|
learnedMove.MoveData.Returns(moveData);
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.ChosenMove.Returns(learnedMove);
|
|
return choice;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a fully mocked test setup where the target's volatile scripts are a real
|
|
/// <see cref="ScriptSet"/> and its last used move is the given one (or none).
|
|
/// </summary>
|
|
private static (Encore script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile, IHitData hitData)
|
|
CreateTestSetup(string? lastUsedMove, bool lastMoveIsReplacement = false, bool lastMoveCantRepeat = false)
|
|
{
|
|
var script = new Encore();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
|
|
// Struggle is recognized through the misc library's replacement choice check, not by name.
|
|
var miscLibrary = Substitute.For<IMiscLibrary>();
|
|
var library = Substitute.For<IDynamicLibrary>();
|
|
library.MiscLibrary.Returns(miscLibrary);
|
|
var battle = Substitute.For<IBattle>();
|
|
battle.Library.Returns(library);
|
|
|
|
var target = Substitute.For<IPokemon>();
|
|
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
|
IScriptSet targetVolatile = new ScriptSet(target);
|
|
target.Volatile.Returns(targetVolatile);
|
|
var targetBattleData = Substitute.For<IPokemonBattleData>();
|
|
// Create the choice before the Returns call; configuring substitutes inside Returns is not allowed.
|
|
var lastMoveChoice = lastUsedMove == null ? null : CreateMoveChoice(lastUsedMove, lastMoveCantRepeat);
|
|
targetBattleData.LastMoveChoice.Returns(lastMoveChoice);
|
|
targetBattleData.Battle.Returns(battle);
|
|
target.BattleData.Returns(targetBattleData);
|
|
if (lastMoveChoice != null)
|
|
miscLibrary.IsReplacementChoice(lastMoveChoice).Returns(lastMoveIsReplacement);
|
|
|
|
var hitData = Substitute.For<IHitData>();
|
|
move.GetHitData(target, 0).Returns(hitData);
|
|
|
|
return (script, move, target, targetVolatile, hitData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Encore temporarily prevents the target from using any move except the last used
|
|
/// move." The target gains the <see cref="EncoreEffect"/> volatile script.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetUsedMove_AddsEncoreEffectToTarget()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, targetVolatile, hitData) = CreateTestSetup("tackle");
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsTrue();
|
|
hitData.DidNotReceive().Fail();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It also fails if the foe hasn't used a move yet." Without a last move choice on the
|
|
/// target, the hit fails and no encore is applied.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetHasNotUsedMove_FailsHit()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, targetVolatile, hitData) = CreateTestSetup(null);
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
hitData.Received(1).Fail();
|
|
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Encore fails if the target's last move was Struggle. A last move that is a replacement
|
|
/// choice (per <see cref="IMiscLibrary.IsReplacementChoice"/>) cannot be encored.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetLastUsedStruggle_FailsHit()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, targetVolatile, hitData) = CreateTestSetup("struggle", true);
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
hitData.Received(1).Fail();
|
|
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Encore fails "if the opponent's last move was Transform, Mimic, Sketch, Mirror Move,
|
|
/// Encore itself, Struggle, or a move with no PP remaining." These moves carry the
|
|
/// <see cref="MoveFlags.CantRepeat"/> flag in the move data, which makes Encore fail.
|
|
/// </summary>
|
|
[Test, Arguments("encore"), Arguments("transform"), Arguments("mimic"), Arguments("sketch"),
|
|
Arguments("mirror_move")]
|
|
public async Task OnSecondaryEffect_TargetLastUsedUnrepeatableMove_FailsHit(string lastUsedMove)
|
|
{
|
|
// Arrange
|
|
var (script, move, target, targetVolatile, hitData) = CreateTestSetup(lastUsedMove, lastMoveCantRepeat: true);
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
hitData.Received(1).Fail();
|
|
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Encore fails "if the opponent's last move was Transform, Mimic, Sketch, Mirror Move,
|
|
/// Encore itself" — integration check: the real Gen7 move data marks exactly these moves with the
|
|
/// <see cref="MoveFlags.CantRepeat"/> flag.
|
|
/// </summary>
|
|
[Test, Arguments("encore"), Arguments("transform"), Arguments("mimic"), Arguments("sketch"),
|
|
Arguments("mirror_move")]
|
|
public async Task MoveData_UnrepeatableMove_HasCantRepeatFlag(string moveName)
|
|
{
|
|
// Arrange
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
|
|
// Act
|
|
var found = library.StaticLibrary.Moves.TryGet(new StringKey(moveName), out var moveData);
|
|
|
|
// Assert
|
|
await Assert.That(found).IsTrue();
|
|
await Assert.That(moveData!.HasFlag(MoveFlags.CantRepeat)).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: without battle data on the target (outside of battle) the effect does nothing and
|
|
/// does not throw.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetHasNoBattleData_DoesNothing()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, targetVolatile, hitData) = CreateTestSetup("tackle");
|
|
target.BattleData.Returns((IPokemonBattleData?)null);
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
hitData.DidNotReceive().Fail();
|
|
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Duration standardized to exactly 3 turns" (Generation V onward). After two
|
|
/// end-of-turn ticks the effect is still active.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnEndTurn_TwoTurnsPassed_EffectStillActive()
|
|
{
|
|
// Arrange
|
|
var owner = Substitute.For<IPokemon>();
|
|
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
|
IScriptSet ownerVolatile = new ScriptSet(owner);
|
|
var effect = new EncoreEffect(owner, new StringKey("tackle"), 3);
|
|
ownerVolatile.Add(effect);
|
|
|
|
// Act
|
|
for (var i = 0; i < 2; i++)
|
|
effect.OnEndTurn(owner, Substitute.For<IBattle>());
|
|
|
|
// Assert
|
|
await Assert.That(ownerVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Duration standardized to exactly 3 turns" (Generation V onward). After the third
|
|
/// end-of-turn tick the effect removes itself.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnEndTurn_ThreeTurnsPassed_EffectRemovesItself()
|
|
{
|
|
// Arrange
|
|
var owner = Substitute.For<IPokemon>();
|
|
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
|
IScriptSet ownerVolatile = new ScriptSet(owner);
|
|
var effect = new EncoreEffect(owner, new StringKey("tackle"), 3);
|
|
ownerVolatile.Add(effect);
|
|
|
|
// Act
|
|
for (var i = 0; i < 3; i++)
|
|
effect.OnEndTurn(owner, Substitute.For<IBattle>());
|
|
|
|
// Assert
|
|
await Assert.That(ownerVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsFalse();
|
|
}
|
|
} |