using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script and its volatile effect script /// . /// Gen VII Bulbapedia behavior: "After the user has used Lock-On and until its effect ends (see table /// below), any accuracy check it makes against the same target will succeed; this means moves will not /// miss, even if the target is in the semi-invulnerable turn of a move such as Dig or Fly.", where the /// effect expires at the end of the next turn. /// public class LockOnTests { private static (LockOn script, IExecutingMove move, IPokemon user, IPokemon target, ScriptSet targetVolatile) CreateTestSetup() { var script = new LockOn(); var move = Substitute.For(); var user = Substitute.For(); move.User.Returns(user); // Use a real script set so the volatile script added by Lock-On can be inspected afterwards, and so // that Script.RemoveSelf calls can be observed through IScriptSet.Contains. var target = Substitute.For(); var targetVolatile = new ScriptSet(target); target.Volatile.Returns(targetVolatile); target.GetScripts().Returns(_ => new ScriptIterator(new List>())); return (script, move, user, target, targetVolatile); } /// /// Bulbapedia: "any accuracy check it makes against the same target will succeed" — the engine runs /// (which implements) over the /// defending Pokémon's scripts, so the effect must be placed on the target for the guarantee /// to ever apply. /// [Test] public async Task OnSecondaryEffect_Hit_AddsLockOnEffectToTarget() { // Arrange var (script, move, _, target, targetVolatile) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// Bulbapedia: "moves will not miss" — an accuracy check by the Pokémon that placed the Lock-On against /// the locked-on target resolves to 255, which the accuracy calculation treats as a guaranteed hit. /// [Test] public async Task ChangeIncomingAccuracy_AttackByLockOnUser_AccuracyGuaranteed() { // Arrange var placer = Substitute.For(); var effect = new LockOnEffect(placer); var attack = Substitute.For(); attack.User.Returns(placer); var accuracy = 90; // Act effect.ChangeIncomingAccuracy(attack, Substitute.For(), 0, ref accuracy); // Assert await Assert.That(accuracy).IsGreaterThanOrEqualTo(255); } /// /// Bulbapedia: it is the accuracy checks of "the user" of Lock-On that are guaranteed — a move used by /// any other Pokémon against the locked-on target is not affected. /// [Test] public async Task ChangeIncomingAccuracy_AttackByOtherPokemon_AccuracyUnchanged() { // Arrange var placer = Substitute.For(); var effect = new LockOnEffect(placer); var attack = Substitute.For(); attack.User.Returns(Substitute.For()); var accuracy = 90; // Act effect.ChangeIncomingAccuracy(attack, Substitute.For(), 0, ref accuracy); // Assert await Assert.That(accuracy).IsEqualTo(90); } /// /// Bulbapedia: "After the user has used Lock-On [...] any accuracy check it makes against the same /// target will succeed" — the effect applied by the move must record the move's user as the /// placer, so that that user's subsequent accuracy checks against the target are the guaranteed ones. /// [Test] public async Task OnSecondaryEffect_AppliedEffect_GuaranteesUsersAccuracyAgainstTarget() { // Arrange var (script, move, _, target, targetVolatile) = CreateTestSetup(); script.OnSecondaryEffect(move, target, 0); await Assert.That(targetVolatile.TryGet(out var effect)).IsTrue(); // Act - the placer attacks the locked-on target var accuracy = 90; effect!.ChangeIncomingAccuracy(move, target, 0, ref accuracy); // Assert await Assert.That(accuracy).IsGreaterThanOrEqualTo(255); } /// /// Bulbapedia: the effect expires at the end of the next turn — at the end of the turn Lock-On was /// used the effect must still be active, so that the move used on the following turn cannot miss. /// [Test] public async Task OnEndTurn_EndOfTurnUsed_EffectStillActive() { // Arrange var (script, move, _, target, targetVolatile) = CreateTestSetup(); script.OnSecondaryEffect(move, target, 0); await Assert.That(targetVolatile.TryGet(out var effect)).IsTrue(); // Act - the end of the turn Lock-On was used effect!.OnEndTurn(Substitute.For(), Substitute.For()); // Assert await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// Bulbapedia: the effect expires at the end of the next turn — after two end-of-turns the effect is /// gone. /// [Test] public async Task OnEndTurn_EndOfNextTurn_EffectExpires() { // Arrange var (script, move, _, target, targetVolatile) = CreateTestSetup(); script.OnSecondaryEffect(move, target, 0); await Assert.That(targetVolatile.TryGet(out var effect)).IsTrue(); // Act - end of the turn Lock-On was used, and end of the next turn effect!.OnEndTurn(Substitute.For(), Substitute.For()); effect.OnEndTurn(Substitute.For(), Substitute.For()); // Assert await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName())).IsFalse(); } }