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