using PkmnLib.Dynamic.Events; 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; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script and its . /// Gen VII Bulbapedia behavior: "The user gains the Submerged status on the turn this move is used, then /// attacks on the following turn. While submerged, the user is invulnerable to most moves but can still be /// hit by Surf and Whirlpool, dealing twice the regular damage." /// public class DiveTests { private static (Dive script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice) CreateTestSetup() { var script = new Dive(); var move = Substitute.For(); var user = Substitute.For(); // Use a real script set so the charge volatile added by Dive can be inspected afterwards. var userVolatile = new ScriptSet(user); user.Volatile.Returns(userVolatile); user.GetScripts().Returns(_ => new ScriptIterator(new List>())); move.User.Returns(user); var moveChoice = Substitute.For(); move.MoveChoice.Returns(moveChoice); var battle = Substitute.For(); battle.EventHook.Returns(new EventHook()); var battleData = Substitute.For(); battleData.Battle.Returns(battle); user.BattleData.Returns(battleData); return (script, move, user, userVolatile, moveChoice); } /// /// Bulbapedia: "The user gains the Submerged status on the turn this move is used, then attacks on the /// following turn." On the first turn the move is prevented from executing. /// [Test] public async Task PreventMove_FirstUse_PreventsMoveForChargeTurn() { // Arrange var (script, move, _, _, _) = CreateTestSetup(); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsTrue(); } /// /// Bulbapedia: "The user gains the Submerged status on the turn this move is used." The charge turn /// attaches the volatile script to the user. /// [Test] public async Task PreventMove_FirstUse_AddsDiveEffectToUser() { // Arrange var (script, move, _, userVolatile, _) = CreateTestSetup(); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// The charge turn is marked on the so the knows /// this choice was the charging turn and does not remove itself for it. /// [Test] public async Task PreventMove_FirstUse_MarksMoveChoiceAsChargeTurn() { // Arrange var (script, move, _, _, moveChoice) = CreateTestSetup(); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert moveChoice.Received(1).SetAdditionalData(new StringKey("dive_charge"), true); await Assert.That(prevent).IsTrue(); } /// /// Bulbapedia: "then attacks on the following turn." When the user already has the /// from the charge turn, the move is not prevented again. /// [Test] public async Task PreventMove_SecondTurn_DoesNotPreventMove() { // Arrange var (script, move, user, userVolatile, _) = CreateTestSetup(); userVolatile.Add(new DiveEffect(user)); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsFalse(); } /// /// Bulbapedia: "then attacks on the following turn." Once the attack lands, the Submerged state /// () is removed from the user. /// [Test] public async Task OnSecondaryEffect_AttackExecuted_RemovesDiveEffect() { // Arrange var (script, move, user, userVolatile, _) = CreateTestSetup(); userVolatile.Add(new DiveEffect(user)); var target = Substitute.For(); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsFalse(); } /// /// Bulbapedia: "While submerged, the user is invulnerable to most moves". The /// blocks incoming hits from moves that cannot hit underwater Pokémon. /// [Test] public async Task BlockIncomingHit_MoveCannotHitUnderwater_BlocksHit() { // Arrange var (script, move, user, userVolatile, _) = CreateTestSetup(); var prevent = false; script.PreventMove(move, ref prevent); await Assert.That(userVolatile.TryGet(out var effect)).IsTrue(); var incomingMove = Substitute.For(); var incomingMoveData = Substitute.For(); incomingMoveData.HasFlag(new StringKey("hit_underwater")).Returns(false); incomingMove.UseMove.Returns(incomingMoveData); // Act var block = false; effect!.BlockIncomingHit(incomingMove, user, 0, ref block); // Assert await Assert.That(block).IsTrue(); } /// /// Bulbapedia: "but can still be hit by Surf and Whirlpool". Moves flagged as able to hit underwater /// Pokémon are not blocked by the . /// [Test] public async Task BlockIncomingHit_SurfLikeMove_DoesNotBlockHit() { // Arrange var (script, move, user, userVolatile, _) = CreateTestSetup(); var prevent = false; script.PreventMove(move, ref prevent); await Assert.That(userVolatile.TryGet(out var effect)).IsTrue(); var incomingMove = Substitute.For(); var incomingMoveData = Substitute.For(); incomingMoveData.HasFlag(new StringKey("hit_underwater")).Returns(true); incomingMove.UseMove.Returns(incomingMoveData); // Act var block = false; effect!.BlockIncomingHit(incomingMove, user, 0, ref block); // Assert await Assert.That(block).IsFalse(); } /// /// Bulbapedia: "can still be hit by Surf and Whirlpool, dealing twice the regular damage." A move /// flagged as effective against underwater Pokémon deals doubled damage to the submerged user. /// [Test] public async Task ChangeIncomingMoveDamage_SurfLikeMove_DamageIsDoubled() { // Arrange var (script, move, user, userVolatile, _) = CreateTestSetup(); var prevent = false; script.PreventMove(move, ref prevent); await Assert.That(userVolatile.TryGet(out var effect)).IsTrue(); var incomingMove = Substitute.For(); var incomingMoveData = Substitute.For(); incomingMoveData.HasFlag(new StringKey("hit_underwater")).Returns(true); incomingMoveData.HasFlag(new StringKey("effective_against_underwater")).Returns(true); incomingMove.UseMove.Returns(incomingMoveData); uint damage = 100; // Act effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage); // Assert await Assert.That(damage).IsEqualTo(200u); } /// /// Bulbapedia: only Surf and Whirlpool deal "twice the regular damage" to the submerged user. A move /// without the effective-against-underwater flag deals regular damage. /// [Test] public async Task ChangeIncomingMoveDamage_RegularMove_DamageIsUnchanged() { // Arrange var (script, move, user, userVolatile, _) = CreateTestSetup(); var prevent = false; script.PreventMove(move, ref prevent); await Assert.That(userVolatile.TryGet(out var effect)).IsTrue(); var incomingMove = Substitute.For(); var incomingMoveData = Substitute.For(); incomingMoveData.HasFlag(new StringKey("effective_against_underwater")).Returns(false); incomingMove.UseMove.Returns(incomingMoveData); uint damage = 100; // Act effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage); // Assert await Assert.That(damage).IsEqualTo(100u); } /// /// When the user's executed choice was not the Dive charge (i.e. the move was disrupted or replaced), /// the Submerged state is removed. /// [Test] public async Task OnAfterMoveChoice_ChoiceIsNotDiveCharge_RemovesDiveEffect() { // Arrange var user = Substitute.For(); var userVolatile = new ScriptSet(user); user.GetScripts().Returns(_ => new ScriptIterator(new List>())); var effect = new DiveEffect(user); userVolatile.Add(effect); var choice = Substitute.For(); choice.AdditionalData.Returns((Dictionary?)null); // Act effect.OnAfterMoveChoice(choice); // Assert await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsFalse(); } /// /// The charging turn itself carries the "dive_charge" marker; the must /// survive that turn so the user stays submerged until the attack turn. /// [Test] public async Task OnAfterMoveChoice_ChoiceIsDiveCharge_KeepsDiveEffect() { // Arrange var user = Substitute.For(); var userVolatile = new ScriptSet(user); user.GetScripts().Returns(_ => new ScriptIterator(new List>())); var effect = new DiveEffect(user); userVolatile.Add(effect); var choice = Substitute.For(); choice.AdditionalData.Returns(new Dictionary { { "dive_charge", true } }); // Act effect.OnAfterMoveChoice(choice); // Assert await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsTrue(); } }