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