This commit is contained in:
194
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/CounterTests.cs
Normal file
194
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/CounterTests.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user