using PkmnLib.Dynamic.Models; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Acupressure chooses one of the target's stats at random and raises it by /// two stages. It can raise either the target's Attack, Defense, Special Attack, Special Defense, Speed, /// accuracy, or evasion stat but will not attempt to raise a stat that is already maximized, meaning that /// the move will fail if all stats are maximized." /// public class AcupressureTests { private static (Acupressure script, IExecutingMove move, IPokemon target, IBattleRandom random, IHitData hitData) CreateTestSetup() { var script = new Acupressure(); var move = Substitute.For(); var target = Substitute.For(); var hitData = Substitute.For(); move.GetHitData(target, 0).Returns(hitData); var random = Substitute.For(); var battle = Substitute.For(); battle.Random.Returns(random); var battleData = Substitute.For(); battleData.Battle.Returns(battle); var user = Substitute.For(); user.BattleData.Returns(battleData); move.User.Returns(user); target.StatBoost.Returns(new StatBoostStatisticSet()); return (script, move, target, random, hitData); } /// /// Helper to extract the stat argument of a ChangeStatBoost call received by the target. /// private static object?[]? GetStatBoostCallArgs(IPokemon target) { var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost"); return call?.GetArguments(); } /// /// Bulbapedia: "Acupressure chooses one of the target's stats at random and raises it by two stages." /// When the random roll selects Attack, the target's Attack is raised by exactly two stages. /// [Test] public async Task OnSecondaryEffect_RandomSelectsAttack_RaisesAttackByTwoStages() { // Arrange var (script, move, target, random, _) = CreateTestSetup(); // Attack is the first entry in the pool of raisable stats (HP is excluded). random.GetInt(Arg.Any(), Arg.Any()).Returns(0); // Act script.OnSecondaryEffect(move, target, 0); // Assert var args = GetStatBoostCallArgs(target); await Assert.That(args).IsNotNull(); await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.Attack); await Assert.That((sbyte)args[1]!).IsEqualTo((sbyte)2); } /// /// Bulbapedia: "It can raise either the target's Attack, Defense, Special Attack, Special Defense, Speed, /// accuracy, or evasion stat" — HP is not one of the raisable stats. When the random roll returns the lowest /// value of its range, the selected stat must be Attack, not HP. /// [Test] public async Task OnSecondaryEffect_RandomMinimumRoll_SelectsAttackNotHp() { // Arrange var (script, move, target, random, _) = CreateTestSetup(); // Return the inclusive lower bound of whatever range is requested. random.GetInt(Arg.Any(), Arg.Any()).Returns(ci => ci.ArgAt(0)); // Act script.OnSecondaryEffect(move, target, 0); // Assert var args = GetStatBoostCallArgs(target); await Assert.That(args).IsNotNull(); await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.Attack); } /// /// Bulbapedia: "It can raise either the target's Attack, Defense, Special Attack, Special Defense, Speed, /// accuracy, or evasion stat". Accuracy and evasion must be selectable: when the random roll returns the /// highest value of its range, the selected stat should be one of the stats beyond Speed in the /// enum (Evasion or Accuracy). /// [Test] public async Task OnSecondaryEffect_RandomMaximumRoll_CanSelectAccuracyOrEvasion() { // Arrange var (script, move, target, random, _) = CreateTestSetup(); // Return the highest value of whatever range is requested (upper bound is exclusive). random.GetInt(Arg.Any(), Arg.Any()).Returns(ci => ci.ArgAt(1) - 1); // Act script.OnSecondaryEffect(move, target, 0); // Assert - the highest selectable stat should be Evasion or Accuracy var args = GetStatBoostCallArgs(target); await Assert.That(args).IsNotNull(); var stat = (Statistic)args![0]!; await Assert.That(stat is Statistic.Evasion or Statistic.Accuracy).IsTrue(); } /// /// Bulbapedia: "the move will fail if all stats are maximized." All raisable stats (Attack through accuracy; /// HP boosts cannot be raised by any move and stay at 0) are at +6, so the move must fail without attempting /// a stat change. /// [Test] public async Task OnSecondaryEffect_AllRaisableStatsMaximized_Fails() { // Arrange var (script, move, target, _, hitData) = CreateTestSetup(); var statBoost = new StatBoostStatisticSet(0, 6, 6, 6, 6, 6) { Evasion = 6, Accuracy = 6 }; target.StatBoost.Returns(statBoost); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse(); } /// /// Bulbapedia: "will not attempt to raise a stat that is already maximized". With the target's Attack at +6 /// and other stats available, Attack must never be the raised stat, regardless of what the random roll /// returns. The rolls cover every index of the six-stat selection pool that remains. /// [Test, Arguments(0), Arguments(1), Arguments(2), Arguments(3), Arguments(4), Arguments(5)] public async Task OnSecondaryEffect_StatAlreadyMaximized_NeverRaisesThatStat(int roll) { // Arrange var (script, move, target, random, _) = CreateTestSetup(); var statBoost = new StatBoostStatisticSet(0, 6, 0, 0, 0, 0); target.StatBoost.Returns(statBoost); random.GetInt(Arg.Any(), Arg.Any()).Returns(roll); // Act script.OnSecondaryEffect(move, target, 0); // Assert - a stat is raised, but never the maximized Attack var args = GetStatBoostCallArgs(target); await Assert.That(args).IsNotNull(); await Assert.That((Statistic)args![0]!).IsNotEqualTo(Statistic.Attack); } }