Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/FireFangTests.cs

200 lines
7.6 KiB
C#

using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="FireFang"/> move script.
/// Gen VII Bulbapedia behavior (Generation IV base text, unchanged through Gen VII): "Fire Fang deals damage
/// and has a 10% chance of burning the opponent. It also has an independent 10% chance of causing the target
/// to flinch, if the user attacks before the target."
/// </summary>
public class FireFangTests
{
/// <summary>
/// Creates a fully mocked test setup for Fire Fang tests. The target has not yet moved this turn when
/// <paramref name="queue"/> contains a choice for it.
/// </summary>
private static (FireFang script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IBattleRandom
random, IScriptSet targetVolatile) CreateTestSetup(BattleChoiceQueue? queue)
{
var script = new FireFang();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var random = Substitute.For<IBattleRandom>();
var battle = Substitute.For<IBattle>();
battle.Random.Returns(random);
battle.ChoiceQueue.Returns(queue);
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = Substitute.For<IScriptSet>();
target.Battle.Returns(battle);
target.Volatile.Returns(targetVolatile);
return (script, move, user, target, random, targetVolatile);
}
/// <summary>
/// Creates a choice queue containing a single yet-to-execute move choice for the given Pokémon.
/// </summary>
private static BattleChoiceQueue CreateQueueWithChoiceFor(IBattlePokemon pokemon)
{
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(pokemon);
return new BattleChoiceQueue([choice]);
}
/// <summary>
/// Helper to check whether a Pokémon received any SetStatus call. (Checked via ReceivedCalls, as arg
/// matchers cannot be used for <see cref="EventBatchId"/> parameters: its parameterless constructor
/// generates a random Guid, which breaks NSubstitute's argument specification binding.)
/// </summary>
private static bool ReceivedSetStatus(IBattlePokemon pokemon) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus");
/// <summary>
/// Bulbapedia: "Fire Fang deals damage and has a 10% chance of burning the opponent."
/// When the burn roll succeeds, the target is burned by the user.
/// </summary>
[Test]
public void OnSecondaryEffect_BurnRollSucceeds_TargetIsBurned()
{
// Arrange
var (script, move, user, target, random, _) = CreateTestSetup(new BattleChoiceQueue([]));
random.EffectChance(10, move, target, 0).Returns(true);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
target.Received(1).SetStatus("burned", user);
}
/// <summary>
/// Bulbapedia: "has a 10% chance of burning the opponent."
/// The burn roll is made with a 10 percent chance.
/// </summary>
[Test]
public void OnSecondaryEffect_BurnRoll_UsesTenPercentChance()
{
// Arrange
var (script, move, _, target, random, _) = CreateTestSetup(new BattleChoiceQueue([]));
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
random.Received(1).EffectChance(10, move, target, 0);
}
/// <summary>
/// Bulbapedia: "has a 10% chance of burning the opponent."
/// When the burn roll fails, the target is not burned.
/// </summary>
[Test]
public async Task OnSecondaryEffect_BurnRollFails_TargetIsNotBurned()
{
// Arrange
var (script, move, _, target, random, _) = CreateTestSetup(new BattleChoiceQueue([]));
random.EffectChance(10, move, target, 0).Returns(false);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(ReceivedSetStatus(target)).IsFalse();
}
/// <summary>
/// Bulbapedia: "It also has an independent 10% chance of causing the target to flinch, if the user
/// attacks before the target."
/// The target still has a queued choice this turn (so the user attacked first), and the flinch roll
/// succeeds: a <see cref="FlinchEffect"/> is added to the target.
/// </summary>
[Test]
public void OnSecondaryEffect_UserAttacksBeforeTargetAndFlinchRollSucceeds_TargetFlinches()
{
// Arrange
var (script, move, _, target, random, targetVolatile) = CreateTestSetup(null);
var queue = CreateQueueWithChoiceFor(target);
target.Battle.ChoiceQueue.Returns(queue);
random.EffectChance(10, move, target, 0).Returns(true, true);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
targetVolatile.Received(1).Add(Arg.Any<FlinchEffect>());
}
/// <summary>
/// Bulbapedia: "It also has an independent 10% chance of causing the target to flinch".
/// The flinch chance is independent of the burn chance: even when the burn roll fails, a successful
/// flinch roll still causes the flinch.
/// </summary>
[Test]
public async Task OnSecondaryEffect_BurnRollFailsButFlinchRollSucceeds_TargetStillFlinches()
{
// Arrange
var (script, move, _, target, random, targetVolatile) = CreateTestSetup(null);
var queue = CreateQueueWithChoiceFor(target);
target.Battle.ChoiceQueue.Returns(queue);
// First roll (burn) fails, second roll (flinch) succeeds.
random.EffectChance(10, move, target, 0).Returns(false, true);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(ReceivedSetStatus(target)).IsFalse();
targetVolatile.Received(1).Add(Arg.Any<FlinchEffect>());
}
/// <summary>
/// Bulbapedia: the flinch can only happen "if the user attacks before the target."
/// The target has already moved this turn (no queued choice for it remains), so no flinch is applied
/// even though the rolls succeed.
/// </summary>
[Test]
public void OnSecondaryEffect_TargetAlreadyMoved_TargetDoesNotFlinch()
{
// Arrange
var (script, move, _, target, random, targetVolatile) = CreateTestSetup(null);
// The queue only holds a choice for some other Pokémon; the target's choice already executed.
var queue = CreateQueueWithChoiceFor(Substitute.For<IBattlePokemon>());
target.Battle.ChoiceQueue.Returns(queue);
random.EffectChance(10, move, target, 0).Returns(true, true);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
}
/// <summary>
/// Technical test: when the battle has no active choice queue, the flinch check is skipped without
/// throwing, while the burn effect can still apply.
/// </summary>
[Test]
public void OnSecondaryEffect_NoChoiceQueue_BurnStillAppliesButNoFlinch()
{
// Arrange
var (script, move, user, target, random, targetVolatile) = CreateTestSetup(null);
random.EffectChance(10, move, target, 0).Returns(true);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
target.Received(1).SetStatus("burned", user);
targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
}
}