using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Conversion 2 will randomly change the user's type to any type that /// either resists or is immune to the type of the last damaging move it was hit by." From Generation V /// onwards "Conversion 2 now targets an adjacent Pokémon" and considers that target's last used move. /// From Generation IV onwards, "Conversion 2 will no longer change the user to any of its current types." /// public class Conversion2Tests { private static readonly IDynamicLibrary Library = LibraryHelpers.LoadLibrary(); private static (Conversion2 script, IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData) CreateTestSetup(TypeIdentifier? lastMoveType) { var script = new Conversion2(); var random = Substitute.For(); random.GetInt().Returns(0); var battle = Substitute.For(); battle.Library.Returns(Library); battle.Random.Returns(random); var userBattleData = Substitute.For(); userBattleData.Battle.Returns(battle); var user = Substitute.For(); user.BattleData.Returns(userBattleData); var targetBattleData = Substitute.For(); if (lastMoveType == null) { targetBattleData.LastMoveChoice.Returns((IMoveChoice?)null); } else { var moveData = Substitute.For(); moveData.MoveType.Returns(lastMoveType.Value); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); var lastChoice = Substitute.For(); lastChoice.ChosenMove.Returns(learnedMove); targetBattleData.LastMoveChoice.Returns(lastChoice); } var target = Substitute.For(); target.BattleData.Returns(targetBattleData); var move = Substitute.For(); move.User.Returns(user); var hitData = Substitute.For(); move.GetHitData(target, 0).Returns(hitData); return (script, move, user, target, hitData); } private static TypeIdentifier GetType(string name) { Library.StaticLibrary.Types.TryGetTypeIdentifier(name, out var type); return type; } /// /// Helper that returns the single type the user was changed to, or null when /// was never called. /// private static TypeIdentifier? GetSetType(IPokemon user) { var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetTypes"); return call != null ? ((IReadOnlyList)call.GetArguments()[0]!).Single() : null; } /// /// Bulbapedia: Conversion 2 changes "the user's type to any type that either resists or is immune to /// the type of the last damaging move" the target used. The chosen type must take less than neutral /// damage from the target's last move type. /// [Test] public async Task OnSecondaryEffect_TargetUsedMove_UserBecomesTypeResistingThatMove() { // Arrange var normalType = GetType("normal"); var (script, move, user, target, _) = CreateTestSetup(normalType); // Act script.OnSecondaryEffect(move, target, 0); // Assert - the new type resists (or is immune to) Normal var newType = GetSetType(user); await Assert.That(newType).IsNotNull(); var effectiveness = Library.StaticLibrary.Types.GetAllEffectivenessFromAttacking(normalType) .Single(x => x.Item1 == newType!.Value).Item2; await Assert.That(effectiveness).IsLessThan(1f); } /// /// Bulbapedia: the move works off "the type of the last damaging move it was hit by" (the target's /// last used move from Generation V onward) — if the target has not used a move, Conversion 2 fails. /// [Test] public void OnSecondaryEffect_TargetHasNoLastMove_Fails() { // Arrange var (script, move, user, target, hitData) = CreateTestSetup(null); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); user.DidNotReceive().SetTypes(Arg.Any>()); } /// /// A last move without a real type (type "none") provides no type to resist, so Conversion 2 fails. /// [Test] public void OnSecondaryEffect_LastMoveHasNoType_Fails() { // Arrange var (script, move, user, target, hitData) = CreateTestSetup(new TypeIdentifier(0, new StringKey("none"))); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); user.DidNotReceive().SetTypes(Arg.Any>()); } /// /// Bulbapedia (Generation IV onwards): "Conversion 2 will no longer change the user to any of its /// current types." When the type that would be rolled is one the user already has, a different /// resisting type must be chosen. /// [Test] public async Task OnSecondaryEffect_RolledTypeIsUsersCurrentType_PicksDifferentType() { // Arrange var normalType = GetType("normal"); var (script, move, user, target, _) = CreateTestSetup(normalType); // With the mocked random always returning 0, the script deterministically picks the resisting type // with the lowest type identifier. Give the user exactly that type. var predictedPick = Library.StaticLibrary.Types.GetAllEffectivenessFromAttacking(normalType) .Where(x => x.effectiveness < 1).OrderBy(x => x.type.Value).First().Item1; user.Types.Returns([predictedPick]); // Act script.OnSecondaryEffect(move, target, 0); // Assert - the user must not be "changed" to a type it already has var newType = GetSetType(user); await Assert.That(newType).IsNotNull(); await Assert.That(newType!.Value).IsNotEqualTo(predictedPick); } }