using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; using PkmnLib.Static; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Autotomize raises the user's Speed stat by two stages and (if the user /// successfully changes its Speed) decreases its weight by 220 lbs. (100 kg). If the user successfully /// changes its weight, the message '<Pokémon> became nimble!' is displayed. Autotomize cannot decrease /// the user's weight below the minimum 0.2 lbs (0.1 kg); if the user's weight would drop below the minimum, /// it becomes the minimum instead. Weight loss from Autotomize stacks". /// public class AutotomizeTests { private static (Autotomize script, IExecutingMove move, IPokemon user, IScriptSet userVolatile, EventHook eventHook) CreateTestSetup(float weightInKg, bool speedRaiseSucceeds, AutotomizeEffect? existingEffect = null) { var script = new Autotomize(); var move = Substitute.For(); var eventHook = new EventHook(); var battle = Substitute.For(); battle.EventHook.Returns(eventHook); var battleData = Substitute.For(); battleData.Battle.Returns(battle); var userVolatile = Substitute.For(); userVolatile.Get().Returns(existingEffect); var user = Substitute.For(); user.BattleData.Returns(battleData); user.Volatile.Returns(userVolatile); user.WeightInKg.Returns(weightInKg); user.ChangeStatBoost(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(speedRaiseSucceeds); move.User.Returns(user); return (script, move, user, userVolatile, eventHook); } private static bool ReceivedStackOrAdd(IScriptSet scriptSet) => scriptSet.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd"); /// /// Bulbapedia: "Autotomize raises the user's Speed stat by two stages". The stat change is self-inflicted. /// [Test] public void OnSecondaryEffect_RaisesUserSpeedByTwoStages() { // Arrange var (script, move, user, _, _) = CreateTestSetup(100f, true); // Act script.OnSecondaryEffect(move, Substitute.For(), 0); // Assert user.Received(1).ChangeStatBoost(Statistic.Speed, 2, true, false); } /// /// Bulbapedia: "(if the user successfully changes its Speed) decreases its weight by 220 lbs. (100 kg). /// If the user successfully changes its weight, the message '<Pokémon> became nimble!' is displayed." /// [Test] public async Task OnSecondaryEffect_SpeedRaiseSucceeds_AddsWeightReductionAndShowsMessage() { // Arrange var (script, move, _, userVolatile, eventHook) = CreateTestSetup(100f, true); DialogEvent? capturedEvent = null; eventHook.Handler += (_, args) => { if (args is DialogEvent dialogEvent) capturedEvent = dialogEvent; }; // Act script.OnSecondaryEffect(move, Substitute.For(), 0); // Assert await Assert.That(ReceivedStackOrAdd(userVolatile)).IsTrue(); await Assert.That(capturedEvent).IsNotNull(); await Assert.That(capturedEvent!.Message).IsEqualTo("pokemon_became_nimble"); } /// /// Bulbapedia: the weight decrease only happens "if the user successfully changes its Speed". When the /// Speed stat cannot be raised (already at +6), no weight reduction is applied and no message is shown. /// [Test] public async Task OnSecondaryEffect_SpeedRaiseFails_NoWeightReductionOrMessage() { // Arrange var (script, move, _, userVolatile, eventHook) = CreateTestSetup(100f, false); var eventFired = false; eventHook.Handler += (_, _) => eventFired = true; // Act script.OnSecondaryEffect(move, Substitute.For(), 0); // Assert await Assert.That(ReceivedStackOrAdd(userVolatile)).IsFalse(); await Assert.That(eventFired).IsFalse(); } /// /// Bulbapedia: "Autotomize cannot decrease the user's weight below the minimum 0.2 lbs (0.1 kg); if the /// user's weight would drop below the minimum, it becomes the minimum instead." A first use on a Pokémon /// lighter than 100 kg still applies the weight reduction (the weight clamps to the minimum). /// [Test] public async Task OnSecondaryEffect_FirstUseOnLightPokemon_StillAppliesWeightReduction() { // Arrange - 50 kg Pokémon, no previous Autotomize use var (script, move, _, userVolatile, _) = CreateTestSetup(50f, true); // Act script.OnSecondaryEffect(move, Substitute.For(), 0); // Assert await Assert.That(ReceivedStackOrAdd(userVolatile)).IsTrue(); } /// /// Bulbapedia: "Weight loss from Autotomize stacks, so using it multiple times will continue to decrease /// the user's weight accordingly until it reaches the minimum weight" and "if the user's weight would drop /// below the minimum, it becomes the minimum instead." A Pokémon that still weighs more than the minimum /// after two stacks must receive another stack (its weight drops to the 0.1 kg minimum). /// [Test] public async Task OnSecondaryEffect_WeightWouldDropBelowMinimum_StillReducesToMinimum() { // Arrange - the user already has two stacks and currently weighs 50 kg var existingEffect = new AutotomizeEffect(); existingEffect.Stack(); var (script, move, _, userVolatile, _) = CreateTestSetup(50f, true, existingEffect); // Act script.OnSecondaryEffect(move, Substitute.For(), 0); // Assert - the weight should still be reduced (to the minimum) await Assert.That(ReceivedStackOrAdd(userVolatile)).IsTrue(); } /// /// Bulbapedia: "Autotomize cannot decrease the user's weight below the minimum 0.2 lbs (0.1 kg)". /// A Pokémon already at the minimum weight cannot lose more weight, so no new stack is added and no /// message is shown; the Speed boost itself is still applied. /// [Test] public async Task OnSecondaryEffect_AlreadyAtMinimumWeight_NoFurtherReductionButSpeedStillRaised() { // Arrange - the user already has a stack and is at the minimum weight var (script, move, user, userVolatile, eventHook) = CreateTestSetup(0.1f, true, new AutotomizeEffect()); var eventFired = false; eventHook.Handler += (_, _) => eventFired = true; // Act script.OnSecondaryEffect(move, Substitute.For(), 0); // Assert user.Received(1).ChangeStatBoost(Statistic.Speed, 2, true, false); await Assert.That(ReceivedStackOrAdd(userVolatile)).IsFalse(); await Assert.That(eventFired).IsFalse(); } }