This commit is contained in:
224
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/FireFangTests.cs
Normal file
224
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/FireFangTests.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
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, IPokemon user, IPokemon target, IBattleRandom random,
|
||||
IScriptSet targetVolatile) CreateTestSetup(BattleChoiceQueue? queue)
|
||||
{
|
||||
var script = new FireFang();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
move.User.Returns(user);
|
||||
|
||||
var random = Substitute.For<IBattleRandom>();
|
||||
var battle = Substitute.For<IBattle>();
|
||||
battle.Random.Returns(random);
|
||||
battle.ChoiceQueue.Returns(queue);
|
||||
var battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.Battle.Returns(battle);
|
||||
|
||||
var target = Substitute.For<IPokemon>();
|
||||
target.BattleData.Returns(battleData);
|
||||
var targetVolatile = Substitute.For<IScriptSet>();
|
||||
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(IPokemon 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(IPokemon 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.BattleData!.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.BattleData!.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<IPokemon>());
|
||||
target.BattleData!.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>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Technical test: if the target has no <see cref="IPokemon.BattleData"/>, the script does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_TargetHasNoBattleData_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var script = new FireFang();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var target = Substitute.For<IPokemon>();
|
||||
target.BattleData.Returns((IPokemonBattleData?)null);
|
||||
var targetVolatile = Substitute.For<IScriptSet>();
|
||||
target.Volatile.Returns(targetVolatile);
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(ReceivedSetStatus(target)).IsFalse();
|
||||
targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user