194 lines
7.1 KiB
C#
194 lines
7.1 KiB
C#
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.Pokemon;
|
|
using PkmnLib.Static.Moves;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="Counter"/> move script and its <see cref="CounterHelperEffect"/>.
|
|
/// Gen VII Bulbapedia behavior: Counter retaliates against the last physical move that damaged the user
|
|
/// this turn, dealing twice the damage the user received to the Pokémon that dealt it. Since Generation II
|
|
/// "Counter now affects all physical moves, dealing double damage"; it fails when the user was not hit by
|
|
/// a physical move first.
|
|
/// </summary>
|
|
public class CounterTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a user whose volatile scripts contain a <see cref="CounterHelperEffect"/> that has recorded
|
|
/// an incoming physical hit of the given damage by <paramref name="attacker"/>.
|
|
/// </summary>
|
|
private static IPokemon CreateUserHitBy(IPokemon? attacker, uint damage, bool physical = true)
|
|
{
|
|
var user = Substitute.For<IPokemon>();
|
|
var userVolatile = new ScriptSet(user);
|
|
user.Volatile.Returns(userVolatile);
|
|
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
|
|
|
var helper = new CounterHelperEffect();
|
|
userVolatile.Add(helper);
|
|
if (attacker != null)
|
|
{
|
|
var incomingMove = Substitute.For<IExecutingMove>();
|
|
var useMove = Substitute.For<IMoveData>();
|
|
useMove.Category.Returns(physical ? MoveCategory.Physical : MoveCategory.Special);
|
|
incomingMove.UseMove.Returns(useMove);
|
|
incomingMove.User.Returns(attacker);
|
|
var incomingHit = Substitute.For<IHitData>();
|
|
incomingHit.Damage.Returns(damage);
|
|
incomingMove.GetHitData(user, 0).Returns(incomingHit);
|
|
helper.OnIncomingHit(incomingMove, user, 0);
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Counter watches for incoming hits over the whole turn: at the start of the turn it attaches its
|
|
/// <see cref="CounterHelperEffect"/> to the user.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnBeforeTurnStart_Always_AddsCounterHelperEffectToUser()
|
|
{
|
|
// Arrange
|
|
var script = new Counter();
|
|
var user = Substitute.For<IPokemon>();
|
|
var userVolatile = new ScriptSet(user);
|
|
user.Volatile.Returns(userVolatile);
|
|
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
|
|
// Act
|
|
script.OnBeforeTurnStart(choice);
|
|
|
|
// Assert
|
|
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<CounterHelperEffect>())).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Counter strikes back at the Pokémon that last hit the user with a physical move — the
|
|
/// move is redirected to that attacker.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeTargets_UserWasHitByPhysicalMove_TargetsTheAttacker()
|
|
{
|
|
// Arrange
|
|
var script = new Counter();
|
|
var attacker = Substitute.For<IPokemon>();
|
|
var user = CreateUserHitBy(attacker, 40);
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
IReadOnlyList<IPokemon?> targets = new IPokemon?[] { Substitute.For<IPokemon>() };
|
|
|
|
// Act
|
|
script.ChangeTargets(choice, ref targets);
|
|
|
|
// Assert
|
|
await Assert.That(targets.Single()).IsEqualTo(attacker);
|
|
choice.DidNotReceive().Fail();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Counter fails when the user has not been hit by a physical move this turn.
|
|
/// </summary>
|
|
[Test]
|
|
public void ChangeTargets_UserWasNotHit_Fails()
|
|
{
|
|
// Arrange
|
|
var script = new Counter();
|
|
var user = CreateUserHitBy(null, 0);
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
IReadOnlyList<IPokemon?> targets = new IPokemon?[] { Substitute.For<IPokemon>() };
|
|
|
|
// Act
|
|
script.ChangeTargets(choice, ref targets);
|
|
|
|
// Assert
|
|
choice.Received(1).Fail();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia (Generation II onwards): "Counter now affects all physical moves, dealing double
|
|
/// damage." The damage dealt is twice the damage of the recorded hit.
|
|
/// </summary>
|
|
[Test, Arguments(40u, 80u), Arguments(1u, 2u), Arguments(123u, 246u)]
|
|
public async Task ChangeMoveDamage_UserWasHitByPhysicalMove_DealsDoubleTheDamageTaken(uint damageTaken,
|
|
uint expectedDamage)
|
|
{
|
|
// Arrange
|
|
var script = new Counter();
|
|
var attacker = Substitute.For<IPokemon>();
|
|
var user = CreateUserHitBy(attacker, damageTaken);
|
|
var move = Substitute.For<IExecutingMove>();
|
|
move.User.Returns(user);
|
|
uint damage = 0;
|
|
|
|
// Act
|
|
script.ChangeMoveDamage(move, attacker, 0, ref damage);
|
|
|
|
// Assert
|
|
await Assert.That(damage).IsEqualTo(expectedDamage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Counter retaliates against the Pokémon that hit the user — against any other target the
|
|
/// hit fails.
|
|
/// </summary>
|
|
[Test]
|
|
public void ChangeMoveDamage_TargetIsNotTheLastAttacker_FailsHit()
|
|
{
|
|
// Arrange
|
|
var script = new Counter();
|
|
var attacker = Substitute.For<IPokemon>();
|
|
var user = CreateUserHitBy(attacker, 40);
|
|
var someoneElse = Substitute.For<IPokemon>();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
move.User.Returns(user);
|
|
var hitData = Substitute.For<IHitData>();
|
|
move.GetHitData(someoneElse, 0).Returns(hitData);
|
|
uint damage = 0;
|
|
|
|
// Act
|
|
script.ChangeMoveDamage(move, someoneElse, 0, ref damage);
|
|
|
|
// Assert
|
|
hitData.Received(1).Fail();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: only physical moves can be countered — the helper records physical hits.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task CounterHelperEffect_PhysicalHit_RecordsAttackerAndDamage()
|
|
{
|
|
// Arrange
|
|
var attacker = Substitute.For<IPokemon>();
|
|
var user = CreateUserHitBy(attacker, 40);
|
|
|
|
// Assert
|
|
var helper = ((IScriptSet)user.Volatile).Get<CounterHelperEffect>();
|
|
await Assert.That(helper!.LastHitBy).IsEqualTo(attacker);
|
|
await Assert.That(helper.LastDamage).IsEqualTo(40u);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia (Generation IV onwards): special moves cannot be countered — the helper ignores
|
|
/// non-physical hits, so Counter fails after a special hit.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task CounterHelperEffect_SpecialHit_IsNotRecorded()
|
|
{
|
|
// Arrange
|
|
var attacker = Substitute.For<IPokemon>();
|
|
var user = CreateUserHitBy(attacker, 40, false);
|
|
|
|
// Assert
|
|
var helper = ((IScriptSet)user.Volatile).Get<CounterHelperEffect>();
|
|
await Assert.That(helper!.LastHitBy).IsNull();
|
|
}
|
|
} |