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