Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HelpingHandTests.cs

199 lines
8.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// Tests for the <see cref="HelpingHand"/> move script and the <see cref="HelpingHandEffect"/> 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."
/// </summary>
public class HelpingHandTests
{
/// <summary>
/// Creates an ally with a real <see cref="ScriptSet"/> as its <see cref="IBattlePokemon.Volatile"/> set, so that
/// adding, stacking, and removing the <see cref="HelpingHandEffect"/> behaves as it would in battle.
/// </summary>
private static (IBattlePokemon ally, ScriptSet volatiles) CreateAllyWithRealVolatileSet()
{
var ally = Substitute.For<IBattlePokemon>();
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<ScriptContainer>[] { volatiles }));
ally.Volatile.Returns(volatiles);
return (ally, volatiles);
}
/// <summary>
/// Applies <see cref="IScriptChangeBasePower.ChangeBasePower"/> 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.
/// </summary>
private static ushort RunChangeBasePowerHooks(ScriptSet volatiles, ushort basePower)
{
var allyMove = Substitute.For<IExecutingMove>();
var opponent = Substitute.For<IBattlePokemon>();
foreach (var container in volatiles)
{
if (container.Script is IScriptChangeBasePower changeBasePower)
changeBasePower.ChangeBasePower(allyMove, opponent, 0, ref basePower);
}
return basePower;
}
/// <summary>
/// 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 <see cref="HelpingHandEffect"/> volatile to the targeted
/// ally.
/// </summary>
[Test]
public async Task OnSecondaryEffect_UsedOnAlly_AddsHelpingHandEffectToAlly()
{
// Arrange
var helpingHand = new HelpingHand();
var move = Substitute.For<IExecutingMove>();
var ally = Substitute.For<IBattlePokemon>();
// 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();
}
/// <summary>
/// Bulbapedia: Helping Hand "will increase the damage done by the user's ally this turn by 50%."
/// The <see cref="HelpingHandEffect"/> multiplies the boosted move's base power by 1.5, truncating fractions.
/// </summary>
[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<IExecutingMove>();
var target = Substitute.For<IBattlePokemon>();
// Act
effect.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(expectedPower);
}
/// <summary>
/// Technical test: boosting a very large base power clamps at <see cref="ushort.MaxValue"/> instead of
/// overflowing.
/// </summary>
[Test]
public async Task ChangeBasePower_LargeBasePower_ClampsAtMaxValue()
{
// Arrange
var effect = new HelpingHandEffect();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IBattlePokemon>();
var basePower = ushort.MaxValue;
// Act
effect.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(ushort.MaxValue);
}
/// <summary>
/// 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).
/// </summary>
[Test]
public async Task ChangeBasePower_TwoHelpingHandsOnSameAlly_BoostsAreCumulative()
{
// Arrange
var helpingHand = new HelpingHand();
var move = Substitute.For<IExecutingMove>();
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);
}
/// <summary>
/// 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).
/// </summary>
[Test]
public async Task ChangeBasePower_ThreeHelpingHandsOnSameAlly_BoostsAreCumulative()
{
// Arrange
var helpingHand = new HelpingHand();
var move = Substitute.For<IExecutingMove>();
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);
}
/// <summary>
/// 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 <see cref="HelpingHandEffect"/> removes itself at the end of the turn.
/// </summary>
[Test]
public async Task OnEndTurn_TurnEnds_EffectRemovesItselfFromAlly()
{
// Arrange
var (ally, volatiles) = CreateAllyWithRealVolatileSet();
var helpingHand = new HelpingHand();
var move = Substitute.For<IExecutingMove>();
helpingHand.OnSecondaryEffect(move, ally, 0);
await Assert.That(volatiles.TryGet<HelpingHandEffect>(out var effect)).IsTrue();
// Act
effect!.OnEndTurn(ally, Substitute.For<IBattle>());
// Assert
await Assert.That(volatiles.TryGet<HelpingHandEffect>(out _)).IsFalse();
}
/// <summary>
/// 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.
/// </summary>
[Test]
public async Task ChangeBasePower_AfterEndOfTurn_BasePowerNoLongerBoosted()
{
// Arrange
var (ally, volatiles) = CreateAllyWithRealVolatileSet();
var helpingHand = new HelpingHand();
var move = Substitute.For<IExecutingMove>();
helpingHand.OnSecondaryEffect(move, ally, 0);
volatiles.TryGet<HelpingHandEffect>(out var effect);
// Act
effect!.OnEndTurn(ally, Substitute.For<IBattle>());
var boosted = RunChangeBasePowerHooks(volatiles, 100);
// Assert
await Assert.That(boosted).IsEqualTo((ushort)100);
}
}