using PkmnLib.Dynamic.Events; 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.Pokemon; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script, a two-turn charge move using /// . /// Gen VII Bulbapedia behavior: "Ice Burn deals damage and has a 30% chance of burning the target." On the /// initial turn, the user enters a charging state while "cloaked in a freezing air"; the actual damage occurs /// on the subsequent turn. "A Power Herb item can bypass the charging phase entirely." /// public class IceBurnTests { /// /// Test helper script that bypasses the charge turn through the /// custom trigger, the same way the Power Herb item /// script does. /// [Script(ScriptCategory.Pokemon, "test_charge_bypasser")] private class ChargeBypasser : Script, IScriptCustomTrigger { public void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { if (eventName == CustomTriggers.BypassChargeMove && args is CustomTriggers.BypassChargeMoveArgs bypassArgs) bypassArgs.Bypass = true; } } /// /// Creates a fully mocked test setup for Ice Burn tests. The user gets a real as /// its volatile set, so the charge volatile added by the move can be inspected afterwards. /// private static (IceBurn script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile, IBattleRandom random, EventHook eventHook) CreateTestSetup() { var script = new IceBurn(); var move = Substitute.For(); move.GetScripts().Returns(_ => new ScriptIterator(new List>())); var user = Substitute.For(); user.GetScripts().Returns(_ => new ScriptIterator(new List>())); var userVolatile = new ScriptSet(user); user.Volatile.Returns(userVolatile); move.User.Returns(user); var eventHook = new EventHook(); var random = Substitute.For(); var battle = Substitute.For(); battle.EventHook.Returns(eventHook); battle.Random.Returns(random); user.Battle.Returns(battle); return (script, move, user, userVolatile, random, eventHook); } /// /// Helper to check whether a Pokémon received any SetStatus call. (Checked via ReceivedCalls, as arg /// matchers cannot be used for parameters.) /// private static bool ReceivedSetStatus(IBattlePokemon pokemon) => pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus"); /// /// Bulbapedia: on the initial turn, the user enters a charging state; the actual damage occurs on the /// subsequent turn. The first use is prevented from executing. /// [Test] public async Task PreventMove_FirstUse_PreventsMoveForChargeTurn() { // Arrange var (script, move, _, _, _, _) = CreateTestSetup(); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsTrue(); } /// /// Bulbapedia: the user enters a charging state on the first turn. The charge turn attaches the /// volatile to the user. /// [Test] public async Task PreventMove_FirstUse_AddsRequireChargeEffectToUser() { // Arrange var (script, move, _, userVolatile, _, _) = CreateTestSetup(); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// Bulbapedia: the user is "cloaked in a freezing air" on the charge turn — the charge turn announces /// itself through a so the battle log can show the charging message. /// [Test] public async Task PreventMove_FirstUse_FiresBeganChargingDialogEvent() { // Arrange var (script, move, user, _, _, eventHook) = CreateTestSetup(); DialogEvent? captured = null; eventHook.Handler += (_, args) => { if (args is DialogEvent dialogEvent) captured = dialogEvent; }; var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(captured).IsNotNull(); await Assert.That(captured!.Message).IsEqualTo("began_charging"); var userParameter = captured!.Parameters?.GetValueOrDefault("user"); await Assert.That(ReferenceEquals(userParameter, user)).IsTrue(); } /// /// Bulbapedia: "The actual damage occurs on the subsequent turn." When the user already carries the /// charge volatile from the previous turn, the move is not prevented again. /// [Test] public async Task PreventMove_UserAlreadyCharged_DoesNotPreventMove() { // Arrange var (script, move, user, userVolatile, _, _) = CreateTestSetup(); userVolatile.Add(script.CreateVolatile(user)); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsFalse(); } /// /// The attack executes on the second turn, ending the charging state: once the move executes, the /// is removed from the user. /// [Test] public async Task OnBeforeMove_ChargeCompleted_RemovesRequireChargeEffect() { // Arrange var (script, move, user, userVolatile, _, _) = CreateTestSetup(); userVolatile.Add(script.CreateVolatile(user)); // Act script.OnBeforeMove(move); // Assert await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsFalse(); } /// /// Bulbapedia: "Ice Burn deals damage and has a 30% chance of burning the target." When the burn roll /// succeeds, the target is burned by the user. /// [Test] public void OnSecondaryEffect_BurnRollSucceeds_TargetIsBurned() { // Arrange var (script, move, user, _, random, _) = CreateTestSetup(); var target = Substitute.For(); random.EffectChance(30, move, target, 0).Returns(true); // Act script.OnSecondaryEffect(move, target, 0); // Assert target.Received(1).SetStatus("burned", user); } /// /// Bulbapedia: "has a 30% chance of burning the target." The burn roll is made with a 30 percent chance. /// [Test] public void OnSecondaryEffect_BurnRoll_UsesThirtyPercentChance() { // Arrange var (script, move, _, _, random, _) = CreateTestSetup(); var target = Substitute.For(); // Act script.OnSecondaryEffect(move, target, 0); // Assert random.Received(1).EffectChance(30, move, target, 0); } /// /// Bulbapedia: "has a 30% chance of burning the target." When the burn roll fails, the target is not /// burned. /// [Test] public async Task OnSecondaryEffect_BurnRollFails_TargetIsNotBurned() { // Arrange var (script, move, _, _, random, _) = CreateTestSetup(); var target = Substitute.For(); random.EffectChance(30, move, target, 0).Returns(false); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(ReceivedSetStatus(target)).IsFalse(); } /// /// Bulbapedia: "A Power Herb item can bypass the charging phase entirely." The bypass flows through the /// custom trigger; a script that sets the bypass flag /// (as the Power Herb item script does) lets the user attack on the first turn without charging. /// [Test] public async Task PreventMove_ChargeBypassedByTrigger_MoveIsNotPrevented() { // Arrange var (script, move, _, _, _, _) = CreateTestSetup(); move.GetScripts().Returns(_ => new ScriptIterator(new List> { new ScriptContainer(new ChargeBypasser()), })); var prevent = false; // Act script.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsFalse(); } }