200 lines
7.6 KiB
C#
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>());
|
|
}
|
|
} |