using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Abilities; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the script, which implements Absorb (and the other HP-draining moves). /// Behavior is verified against the Bulbapedia page for Absorb. /// public class DrainTests { /// /// Creates a fully mocked test setup for Drain tests. /// private static (Drain drain, IExecutingMove move, IPokemon target, IPokemon user) CreateTestSetup(uint damage, bool holdsBigRoot = false, Script[]? targetScripts = null) { var drain = new Drain(); 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 (drain, 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; } /// /// Helper to extract the damage amount from the user's received Damage calls. /// private static uint? GetDamageAmount(IPokemon user) { var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage"); return call != null ? (uint)call.GetArguments()[0]! : null; } /// /// Helper to extract the damage source from the user's received Damage calls. /// private static DamageSource? GetDamageSource(IPokemon user) { var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage"); return call != null ? (DamageSource)call.GetArguments()[1]! : null; } /// /// Bulbapedia: "Absorb inflicts damage, and up to 50% of the damage dealt to the target is restored to the /// user as HP". /// [Test] public async Task OnSecondaryEffect_DamageDealt_UserHealsHalfOfDamage() { // Arrange var (drain, move, target, user) = CreateTestSetup(100); // Act drain.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetHealAmount(user)).IsEqualTo(50u); } /// /// Bulbapedia: "up to 50% of the damage dealt to the target is restored to the user as HP". /// Tests various damage values to ensure proper integer truncation of the halved amount. /// [Test, Arguments(200u, 100u), Arguments(51u, 25u), Arguments(99u, 49u), Arguments(3u, 1u)] public async Task OnSecondaryEffect_DamageDealt_HealCalculation(uint damage, uint expectedHeal) { // Arrange var (drain, move, target, user) = CreateTestSetup(damage); // Act drain.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetHealAmount(user)).IsEqualTo(expectedHeal); } /// /// Bulbapedia: "up to 50% of the damage dealt to the target is restored to the user as HP (but not less /// than 1 HP)". /// A move dealing 1 damage should still restore 1 HP. /// [Test] public async Task OnSecondaryEffect_DamageOfOne_HealsAtLeastOneHp() { // Arrange var (drain, move, target, user) = CreateTestSetup(1); // Act drain.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetHealAmount(user)).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 (drain, move, target, user) = CreateTestSetup(100, true); // Act drain.OnSecondaryEffect(move, target, 0); // Assert - 65% of 100 damage await Assert.That(GetHealAmount(user)).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 (drain, move, target, user) = CreateTestSetup(100, targetScripts: [new LiquidOoze()]); // Act drain.OnSecondaryEffect(move, target, 0); // Assert - user takes the 50 HP it would have gained, and is not healed await Assert.That(GetDamageAmount(user)).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 (drain, move, target, user) = CreateTestSetup(100, targetScripts: [new LiquidOoze()]); // Act drain.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetDamageSource(user)).IsEqualTo(DamageSource.Misc); } /// /// Bulbapedia: "the user will lose the amount of HP it would have gained instead." /// With a Big Root held, the amount it would have gained is 65% of the damage, so that is what is lost. /// [Test] public async Task OnSecondaryEffect_LiquidOozeAndBigRoot_UserLosesBoostedAmount() { // Arrange var (drain, move, target, user) = CreateTestSetup(100, true, [new LiquidOoze()]); // Act drain.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetDamageAmount(user)).IsEqualTo(65u); } /// /// Technical test: without initialization parameters the drain modifier defaults to 50%, matching Absorb. /// [Test] public async Task OnInitialize_NullParameters_DrainModifierDefaultsToHalf() { // Arrange var drain = new Drain(); // Act drain.OnInitialize(null); // Assert await Assert.That(drain.DrainModifier).IsEqualTo(0.5f); } /// /// Technical test: a "drain_modifier" parameter overrides the default drain fraction. /// [Test] public async Task OnInitialize_DrainModifierParameter_OverridesDefault() { // Arrange var drain = new Drain(); var parameters = new Dictionary { { "drain_modifier", 0.75f } }; // Act drain.OnInitialize(parameters); // Assert await Assert.That(drain.DrainModifier).IsEqualTo(0.75f); } /// /// Bulbapedia: "up to 50% of the damage dealt to the target is restored to the user as HP". /// Integration check: initializing the script with Absorb's actual effect parameters from the Gen7 data /// results in a 50% drain. /// [Test] public async Task OnSecondaryEffect_InitializedWithAbsorbData_HealsHalfOfDamage() { // Arrange var library = LibraryHelpers.LoadLibrary(); await Assert.That(library.StaticLibrary.Moves.TryGet("absorb", out var absorb)).IsTrue(); var (drain, move, target, user) = CreateTestSetup(100); // Act drain.OnInitialize(absorb!.SecondaryEffect!.Parameters); drain.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetHealAmount(user)).IsEqualTo(50u); } }