Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/CounterTests.cs
Deukhoofd 6a82c20cf2
All checks were successful
Build / Build (push) Successful in 1m57s
More tests, more fixes
2026-07-05 18:26:55 +02:00

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