Many more tests and fixes
All checks were successful
Build / Build (push) Successful in 3m12s

This commit is contained in:
2026-08-27 17:11:12 +02:00
parent 32b3ef9c4a
commit d2a82b5fe3
204 changed files with 20255 additions and 242 deletions

View File

@@ -0,0 +1,199 @@
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="IPokemon.Volatile"/> set, so that
/// adding, stacking, and removing the <see cref="HelpingHandEffect"/> behaves as it would in battle.
/// </summary>
private static (IPokemon ally, ScriptSet volatiles) CreateAllyWithRealVolatileSet()
{
var ally = Substitute.For<IPokemon>();
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<IPokemon>();
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<IPokemon>();
// 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<IPokemon>();
// 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<IPokemon>();
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);
}
}