using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Plugin.Gen7.Scripts; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Side; using PkmnLib.Plugin.Gen7.Scripts.Weather; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Aurora Veil reduces the damage done to the user by physical and special /// moves for five turns [...]. The move cannot be activated unless hail [...] is present. [...] Holding /// Light Clay extends the duration from 5 to 8 turns." /// public class AuroraVeilTests { /// /// Test helper script that extends the Aurora Veil duration through the custom trigger, the same way the /// Light Clay item script does. /// [Script(ScriptCategory.Pokemon, "test_aurora_veil_duration_extender")] private class DurationExtender : Script, IScriptCustomTrigger { public void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { if (eventName == CustomTriggers.AuroraVeilDuration && args is CustomTriggers.AuroraVeilDurationArgs d) d.Duration = 8; } } private static (AuroraVeil script, IExecutingMove move, IPokemon target, IPokemon user, IScriptSet sideScripts, IHitData hitData) CreateTestSetup(StringKey? weather) { var script = new AuroraVeil(); var move = Substitute.For(); var target = Substitute.For(); var hitData = Substitute.For(); move.GetHitData(target, 0).Returns(hitData); var battle = Substitute.For(); battle.WeatherName.Returns(weather); var sideScripts = Substitute.For(); // Mimic the real ScriptSet: invoke the factory and hand back a container holding the new script. sideScripts.StackOrAdd(Arg.Any(), Arg.Any>()) .Returns(ci => new ScriptContainer(ci.Arg>()()!)); var side = Substitute.For(); side.VolatileScripts.Returns(sideScripts); battle.Sides.Returns(new[] { side }); var battleData = Substitute.For(); battleData.Battle.Returns(battle); battleData.SideIndex.Returns((byte)0); var user = Substitute.For(); user.BattleData.Returns(battleData); user.GetScripts().Returns(_ => new ScriptIterator(new List>())); move.User.Returns(user); return (script, move, target, user, sideScripts, hitData); } /// /// Helper to extract the effect script instance created through StackOrAdd on the side's volatile scripts. /// private static AuroraVeilEffect? GetAddedEffect(IScriptSet sideScripts) { var call = sideScripts.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "StackOrAdd"); if (call == null) return null; var factory = (Func)call.GetArguments()[1]!; return factory() as AuroraVeilEffect; } /// /// Bulbapedia: "The move cannot be activated unless hail [...] is present." /// Without any weather, the move fails and no effect is added to the side. /// [Test] public async Task OnSecondaryEffect_NoWeather_Fails() { // Arrange var (script, move, target, _, sideScripts, hitData) = CreateTestSetup(null); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); await Assert.That(sideScripts.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsFalse(); } /// /// Bulbapedia: "The move cannot be activated unless hail [...] is present." /// Under a different weather (e.g. rain), the move also fails. /// [Test] public async Task OnSecondaryEffect_NonHailWeather_Fails() { // Arrange var (script, move, target, _, sideScripts, hitData) = CreateTestSetup(new StringKey("rain")); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); await Assert.That(sideScripts.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsFalse(); } /// /// Bulbapedia: "Aurora Veil reduces the damage done to the user by physical and special moves for five /// turns". During hail, the Aurora Veil effect is added to the user's side with a duration of 5 turns. /// [Test] public async Task OnSecondaryEffect_Hail_AddsEffectWithFiveTurns() { // Arrange var (script, move, target, _, sideScripts, hitData) = CreateTestSetup(ScriptUtils.ResolveName()); // Act script.OnSecondaryEffect(move, target, 0); // Assert var effect = GetAddedEffect(sideScripts); await Assert.That(effect).IsNotNull(); await Assert.That(effect!.NumberOfTurns).IsEqualTo(5); hitData.DidNotReceive().Fail(); } /// /// Bulbapedia: "If a Light Clay is held when Aurora Veil is used, it will extend the duration of Aurora /// Veil from 5 to 8 turns." The duration extension flows through the AuroraVeilDuration custom trigger; /// a script on the user that sets the duration to 8 results in an effect lasting 8 turns. /// [Test] public async Task OnSecondaryEffect_DurationExtendedByTrigger_AddsEffectWithEightTurns() { // Arrange var (script, move, target, user, sideScripts, _) = CreateTestSetup(ScriptUtils.ResolveName()); user.GetScripts().Returns(_ => new ScriptIterator(new List> { new ScriptContainer(new DurationExtender()), })); // Act script.OnSecondaryEffect(move, target, 0); // Assert - the created effect is initialized with the extended duration var call = sideScripts.ReceivedCalls().First(c => c.GetMethodInfo().Name == "StackOrAdd"); var effect = (AuroraVeilEffect)((Func)call.GetArguments()[1]!)()!; await Assert.That(effect.NumberOfTurns).IsEqualTo(8); } /// /// Technical test: outside of battle (no battle data) the script returns without throwing. /// [Test] public async Task OnSecondaryEffect_NoBattleData_DoesNothing() { // Arrange var script = new AuroraVeil(); var move = Substitute.For(); var target = Substitute.For(); var user = Substitute.For(); user.BattleData.Returns((IPokemonBattleData?)null); move.User.Returns(user); // Act script.OnSecondaryEffect(move, target, 0); // Assert - no hit data was touched await Assert.That(move.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "GetHitData")).IsFalse(); } }