using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
///
/// Tests for the move script and the volatile script it
/// applies to the ally.
/// Gen VII Bulbapedia behavior: "Helping Hand is an increased priority move that will increase the damage done by
/// the user's ally this turn by 50%. It will fail if there is no adjacent ally, or if the ally has already acted
/// this turn." and "If Helping Hand is used multiple times in a turn, all targeting the same Pokémon, the boosts
/// are cumulative."
///
public class HelpingHandTests
{
///
/// Creates an ally with a real as its set, so that
/// adding, stacking, and removing the behaves as it would in battle.
///
private static (IPokemon ally, ScriptSet volatiles) CreateAllyWithRealVolatileSet()
{
var ally = Substitute.For();
var volatiles = new ScriptSet(ally);
// ScriptSet.Add runs the IScriptPreventVolatileAdd hook over the owner's scripts; give the mock a real
// iterator so that hook pass runs (empty of blockers here).
ally.GetScripts().Returns(_ => new ScriptIterator(new IEnumerable[] { volatiles }));
ally.Volatile.Returns(volatiles);
return (ally, volatiles);
}
///
/// Applies of every script in the ally's volatile set to
/// the given base power, as the battle engine would when the ally uses its move.
///
private static ushort RunChangeBasePowerHooks(ScriptSet volatiles, ushort basePower)
{
var allyMove = Substitute.For();
var opponent = Substitute.For();
foreach (var container in volatiles)
{
if (container.Script is IScriptChangeBasePower changeBasePower)
changeBasePower.ChangeBasePower(allyMove, opponent, 0, ref basePower);
}
return basePower;
}
///
/// Bulbapedia: "Helping Hand is an increased priority move that will increase the damage done by the user's
/// ally this turn by 50%." Using the move attaches the volatile to the targeted
/// ally.
///
[Test]
public async Task OnSecondaryEffect_UsedOnAlly_AddsHelpingHandEffectToAlly()
{
// Arrange
var helpingHand = new HelpingHand();
var move = Substitute.For();
var ally = Substitute.For();
// Act
helpingHand.OnSecondaryEffect(move, ally, 0);
// Assert
var addCall = ally.Volatile.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Add");
await Assert.That(addCall).IsNotNull();
await Assert.That(addCall!.GetArguments()[0] is HelpingHandEffect).IsTrue();
}
///
/// Bulbapedia: Helping Hand "will increase the damage done by the user's ally this turn by 50%."
/// The multiplies the boosted move's base power by 1.5, truncating fractions.
///
[Test, Arguments((ushort)100, (ushort)150), Arguments((ushort)60, (ushort)90), Arguments((ushort)65, (ushort)97),
Arguments((ushort)1, (ushort)1)]
public async Task ChangeBasePower_EffectActive_BasePowerIncreasedByFiftyPercent(ushort basePower,
ushort expectedPower)
{
// Arrange
var effect = new HelpingHandEffect();
var move = Substitute.For();
var target = Substitute.For();
// Act
effect.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(expectedPower);
}
///
/// Technical test: boosting a very large base power clamps at instead of
/// overflowing.
///
[Test]
public async Task ChangeBasePower_LargeBasePower_ClampsAtMaxValue()
{
// Arrange
var effect = new HelpingHandEffect();
var move = Substitute.For();
var target = Substitute.For();
var basePower = ushort.MaxValue;
// Act
effect.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(ushort.MaxValue);
}
///
/// Bulbapedia: "If Helping Hand is used multiple times in a turn, all targeting the same Pokémon, the boosts
/// are cumulative. The move power will increase by 125% if there are two Helping Hands".
/// Two Helping Hands on the same ally should therefore boost 100 base power to 225 (1.5 × 1.5).
///
[Test]
public async Task ChangeBasePower_TwoHelpingHandsOnSameAlly_BoostsAreCumulative()
{
// Arrange
var helpingHand = new HelpingHand();
var move = Substitute.For();
var (ally, volatiles) = CreateAllyWithRealVolatileSet();
// Act - two different Pokémon use Helping Hand on the same ally in the same turn
helpingHand.OnSecondaryEffect(move, ally, 0);
helpingHand.OnSecondaryEffect(move, ally, 0);
var boosted = RunChangeBasePowerHooks(volatiles, 100);
// Assert - 100 × 1.5 × 1.5 = 225
await Assert.That(boosted).IsEqualTo((ushort)225);
}
///
/// Bulbapedia: "The move power will increase by ... 237.5% if there are three [Helping Hands]."
/// Three Helping Hands on the same ally should boost 100 base power to 337 (1.5³ = 3.375, truncated).
///
[Test]
public async Task ChangeBasePower_ThreeHelpingHandsOnSameAlly_BoostsAreCumulative()
{
// Arrange
var helpingHand = new HelpingHand();
var move = Substitute.For();
var (ally, volatiles) = CreateAllyWithRealVolatileSet();
// Act
helpingHand.OnSecondaryEffect(move, ally, 0);
helpingHand.OnSecondaryEffect(move, ally, 0);
helpingHand.OnSecondaryEffect(move, ally, 0);
var boosted = RunChangeBasePowerHooks(volatiles, 100);
// Assert - 100 × 1.5 × 1.5 × 1.5 = 337.5, truncated to 337
await Assert.That(boosted).IsEqualTo((ushort)337);
}
///
/// Bulbapedia: Helping Hand increases "the damage done by the user's ally this turn" — the boost only lasts
/// for the turn it was used, so the removes itself at the end of the turn.
///
[Test]
public async Task OnEndTurn_TurnEnds_EffectRemovesItselfFromAlly()
{
// Arrange
var (ally, volatiles) = CreateAllyWithRealVolatileSet();
var helpingHand = new HelpingHand();
var move = Substitute.For();
helpingHand.OnSecondaryEffect(move, ally, 0);
await Assert.That(volatiles.TryGet(out var effect)).IsTrue();
// Act
effect!.OnEndTurn(ally, Substitute.For());
// Assert
await Assert.That(volatiles.TryGet(out _)).IsFalse();
}
///
/// Bulbapedia: the 50% boost applies "this turn" — after the effect has been removed at the end of the turn,
/// the ally's moves are no longer boosted.
///
[Test]
public async Task ChangeBasePower_AfterEndOfTurn_BasePowerNoLongerBoosted()
{
// Arrange
var (ally, volatiles) = CreateAllyWithRealVolatileSet();
var helpingHand = new HelpingHand();
var move = Substitute.For();
helpingHand.OnSecondaryEffect(move, ally, 0);
volatiles.TryGet(out var effect);
// Act
effect!.OnEndTurn(ally, Substitute.For());
var boosted = RunChangeBasePowerHooks(volatiles, 100);
// Assert
await Assert.That(boosted).IsEqualTo((ushort)100);
}
}