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