using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Behavior is verified against the Bulbapedia page for Freeze-Dry (Generation VII behavior). /// The effectiveness tests use the real Gen7 type chart, as the script's core behavior is rewriting /// type effectiveness against Water-type targets. /// public class FreezeDryTests { /// /// Creates a test setup with the real Gen7 type library, and a target in battle with the given types. /// private static (FreezeDry script, IExecutingMove move, IBattlePokemon target, IDynamicLibrary library) CreateEffectivenessSetup(params string[] targetTypes) { var script = new FreezeDry(); var library = LibraryHelpers.LoadLibrary(); var battle = Substitute.For(); battle.Library.Returns(library); var target = Substitute.For(); target.Types.Returns(targetTypes.Select(name => GetTypeId(library, name)).ToList()); target.Battle.Returns(battle); var move = Substitute.For(); var hitData = Substitute.For(); hitData.Type.Returns(GetTypeId(library, "ice")); move.GetHitData(Arg.Any(), Arg.Any()).Returns(hitData); var user = Substitute.For(); move.User.Returns(user); return (script, move, target, library); } /// /// Creates a fully mocked test setup for the secondary (freeze) effect tests, with a battle random /// that reports the given result for effect chance rolls. /// private static (FreezeDry script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user, IBattleRandom random) CreateSecondaryEffectSetup(bool effectChanceResult) { var script = new FreezeDry(); var random = Substitute.For(); random.EffectChance(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(effectChanceResult); var battle = Substitute.For(); battle.Random.Returns(random); var target = Substitute.For(); target.Battle.Returns(battle); var move = Substitute.For(); var user = Substitute.For(); move.User.Returns(user); return (script, move, target, user, random); } /// /// Helper to resolve a type identifier from the loaded Gen7 library. /// private static TypeIdentifier GetTypeId(IDynamicLibrary library, string name) { library.StaticLibrary.Types.TryGetTypeIdentifier(name, out var type); return type; } /// /// Bulbapedia: "Freeze-Dry will always deal supereffective (x2) damage to the Water-type, even during /// Inverse Battles or if its type is changed." /// Against a pure Water-type target, the effectiveness is always rewritten to exactly 2, regardless of /// the naturally calculated incoming effectiveness. /// [Test, Arguments(0.5f), Arguments(1f), Arguments(2f)] public async Task ChangeEffectiveness_PureWaterTarget_EffectivenessAlwaysBecomesDouble(float initialEffectiveness) { // Arrange var (script, move, target, _) = CreateEffectivenessSetup("water"); var effectiveness = initialEffectiveness; // Act script.ChangeEffectiveness(move, target, 0, ref effectiveness); // Assert await Assert.That(effectiveness).IsEqualTo(2f); } /// /// Bulbapedia: "Freeze-Dry will always deal supereffective (x2) damage to the Water-type". /// The x2 replaces only Ice's normal matchup against the Water type; the move's regular effectiveness /// against the target's other type still applies. For example, against a Water/Flying Pokémon such as /// Gyarados, Freeze-Dry is 4x effective (2x from Water, 2x from Ice against Flying). /// [Test, Arguments("flying", 4f), Arguments("grass", 4f), Arguments("ground", 4f), Arguments("ice", 1f), Arguments("steel", 1f)] public async Task ChangeEffectiveness_DualTypeWaterTarget_DoubleAgainstWaterTimesOtherTypeEffectiveness( string otherType, float expected) { // Arrange var (script, move, target, library) = CreateEffectivenessSetup("water", otherType); // Start from the naturally calculated effectiveness of an Ice-type move against the target. var effectiveness = library.StaticLibrary.Types.GetEffectiveness(GetTypeId(library, "ice"), target.Types); // Act script.ChangeEffectiveness(move, target, 0, ref effectiveness); // Assert await Assert.That(effectiveness).IsEqualTo(expected); } /// /// Bulbapedia: the guaranteed super effectiveness applies "to the Water-type". Against a target without /// the Water type, the effectiveness must be left untouched. /// [Test] public async Task ChangeEffectiveness_NonWaterTarget_EffectivenessUnchanged() { // Arrange var (script, move, target, _) = CreateEffectivenessSetup("grass"); var effectiveness = 2f; // Act script.ChangeEffectiveness(move, target, 0, ref effectiveness); // Assert await Assert.That(effectiveness).IsEqualTo(2f); } /// /// Bulbapedia (Generation VI to VIII): "Freeze-Dry has a 10% chance of freezing the target." /// When the effect chance roll succeeds, the target is frozen, with the user as the origin. /// [Test] public async Task OnSecondaryEffect_EffectChanceSucceeds_TargetIsFrozen() { // Arrange var (script, move, target, user, _) = CreateSecondaryEffectSetup(true); // Act script.OnSecondaryEffect(move, target, 0); // Assert var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetStatus"); await Assert.That(call).IsNotNull(); await Assert.That((StringKey)call!.GetArguments()[0]!).IsEqualTo(new StringKey("frozen")); await Assert.That(ReferenceEquals(call.GetArguments()[1], user)).IsTrue(); } /// /// Bulbapedia (Generation VI to VIII): "Freeze-Dry has a 10% chance of freezing the target." /// When the effect chance roll fails, no status may be set. /// [Test] public async Task OnSecondaryEffect_EffectChanceFails_TargetNotFrozen() { // Arrange var (script, move, target, _, _) = CreateSecondaryEffectSetup(false); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse(); } /// /// Bulbapedia (Generation VI to VIII): "Freeze-Dry has a 10% chance of freezing the target." /// The effect chance roll must be made with a 10% chance. /// [Test] public async Task OnSecondaryEffect_Always_RollsTenPercentEffectChance() { // Arrange var (script, move, target, _, random) = CreateSecondaryEffectSetup(false); // Act script.OnSecondaryEffect(move, target, 0); // Assert var call = random.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "EffectChance"); await Assert.That(call).IsNotNull(); await Assert.That((float)call!.GetArguments()[0]!).IsEqualTo(10f); } }