using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Abilities; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static.Species; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Dream Eater only works if the target is asleep; otherwise, it does /// nothing", and "50% of the damage dealt to the target is restored to the user as HP". /// public class DreamEaterTests { /// /// Creates a fully mocked test setup for Dream Eater tests. /// private static (DreamEater script, IExecutingMove move, IPokemon target, IPokemon user) CreateTestSetup(uint damage, bool holdsBigRoot = false, Script[]? targetScripts = null) { var script = new DreamEater(); var move = Substitute.For(); var target = Substitute.For(); var hitData = Substitute.For(); hitData.Damage.Returns(damage); move.GetHitData(target, 0).Returns(hitData); // RunScriptHook iterates the target's scripts; give the mock a real iterator so the // hook pass runs (empty unless the test attaches scripts such as Liquid Ooze). var containers = (targetScripts ?? []).Select(IEnumerable (s) => new ScriptContainer(s)) .ToArray(); target.GetScripts().Returns(_ => new ScriptIterator(containers)); var user = Substitute.For(); if (holdsBigRoot) user.HasHeldItem("big_root").Returns(true); move.User.Returns(user); return (script, move, target, user); } /// /// Helper to extract the heal amount from the user's received Heal calls. /// private static uint? GetHealAmount(IPokemon user) { var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal"); return call != null ? (uint)call.GetArguments()[0]! : null; } /// /// Bulbapedia: "Dream Eater only works if the target is asleep; otherwise, it does nothing." /// The outgoing hit is blocked when the target does not have the sleep status. /// [Test] public async Task BlockOutgoingHit_TargetNotAsleep_BlocksHit() { // Arrange var (script, move, target, _) = CreateTestSetup(100); target.HasStatus(new StringKey("asleep")).Returns(false); var block = false; // Act script.BlockOutgoingHit(move, target, 0, ref block); // Assert await Assert.That(block).IsTrue(); } /// /// Bulbapedia: "Dream Eater only works if the target is asleep". A sleeping target can be hit. /// [Test] public async Task BlockOutgoingHit_TargetAsleep_DoesNotBlockHit() { // Arrange var (script, move, target, _) = CreateTestSetup(100); target.HasStatus(new StringKey("asleep")).Returns(true); var block = false; // Act script.BlockOutgoingHit(move, target, 0, ref block); // Assert await Assert.That(block).IsFalse(); } /// /// Bulbapedia: "Dream Eater also affects targets with the Comatose Ability, which act as though they /// are asleep without the status condition." /// [Test] public async Task BlockOutgoingHit_TargetHasComatose_DoesNotBlockHit() { // Arrange var (script, move, target, _) = CreateTestSetup(100); target.HasStatus(new StringKey("asleep")).Returns(false); var comatose = Substitute.For(); comatose.Name.Returns(new StringKey("comatose")); target.ActiveAbility.Returns(comatose); var block = false; // Act script.BlockOutgoingHit(move, target, 0, ref block); // Assert await Assert.That(block).IsFalse(); } /// /// Bulbapedia: "50% of the damage dealt to the target is restored to the user as HP". /// [Test, Arguments(100u, 50u), Arguments(200u, 100u), Arguments(51u, 25u), Arguments(99u, 49u)] public async Task OnSecondaryEffect_DamageDealt_UserHealsHalfOfDamage(uint damage, uint expectedHeal) { // Arrange var (script, move, target, user) = CreateTestSetup(damage); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(expectedHeal); } /// /// Bulbapedia: "if the attack deals 1 HP of damage, 1 HP will be restored to the user." /// [Test] public async Task OnSecondaryEffect_DamageOfOne_HealsAtLeastOneHp() { // Arrange var (script, move, target, user) = CreateTestSetup(1); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(1u); } /// /// Bulbapedia: "If the user is holding a Big Root, the HP restored is increased by 30% (making the /// restored HP 65% of the damage dealt)." /// [Test] public async Task OnSecondaryEffect_UserHoldingBigRoot_HealIncreasedByThirtyPercent() { // Arrange var (script, move, target, user) = CreateTestSetup(100, true); // Act script.OnSecondaryEffect(move, target, 0); // Assert - 65% of 100 damage await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(65u); } /// /// Bulbapedia: "When used on a Pokémon with the Liquid Ooze Ability, the user will lose the amount of /// HP it would have gained instead." /// [Test] public async Task OnSecondaryEffect_TargetHasLiquidOoze_UserLosesHpInsteadOfHealing() { // Arrange var (script, move, target, user) = CreateTestSetup(100, targetScripts: [new LiquidOoze()]); // Act script.OnSecondaryEffect(move, target, 0); // Assert - user takes the 50 HP it would have gained, and is not healed var damageCall = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage"); await Assert.That(damageCall).IsNotNull(); await Assert.That((uint)damageCall!.GetArguments()[0]!).IsEqualTo(50u); await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Heal")).IsFalse(); } /// /// Bulbapedia: "the user will lose the amount of HP it would have gained instead." The HP loss caused /// by Liquid Ooze is indirect damage, not move damage. /// [Test] public async Task OnSecondaryEffect_TargetHasLiquidOoze_UsesMiscDamageSource() { // Arrange var (script, move, target, user) = CreateTestSetup(100, targetScripts: [new LiquidOoze()]); // Act script.OnSecondaryEffect(move, target, 0); // Assert var damageCall = user.ReceivedCalls().First(c => c.GetMethodInfo().Name == "Damage"); await Assert.That((DamageSource)damageCall.GetArguments()[1]!).IsEqualTo(DamageSource.Misc); } }