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