199 lines
7.5 KiB
C#
199 lines
7.5 KiB
C#
using PkmnLib.Dynamic.Models;
|
|
using PkmnLib.Dynamic.ScriptHandling;
|
|
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Side;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="DoomDesire"/> move script and its <see cref="DoomDesireEffect"/>.
|
|
/// Gen VII Bulbapedia behavior: "On the turn Doom Desire is selected, this attack will do nothing other
|
|
/// than state that the user has chosen Doom Desire as its destiny. Two turns later, Doom Desire will do
|
|
/// damage against the target."
|
|
/// </summary>
|
|
public class DoomDesireTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a fully mocked test setup where the target is at the given position on a side whose
|
|
/// volatile scripts are a real <see cref="ScriptSet"/>.
|
|
/// </summary>
|
|
private static (DoomDesire script, IExecutingMove move, IPokemon target, IBattleSide side, ScriptSet sideScripts,
|
|
IHitData hitData) CreateTestSetup(byte position = 0, uint damage = 100)
|
|
{
|
|
var script = new DoomDesire();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
|
|
var side = Substitute.For<IBattleSide>();
|
|
var sideScripts = new ScriptSet(side);
|
|
side.VolatileScripts.Returns(sideScripts);
|
|
side.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
|
var battle = Substitute.For<IBattle>();
|
|
battle.Sides.Returns(new[] { side });
|
|
|
|
var target = Substitute.For<IPokemon>();
|
|
var battleData = Substitute.For<IPokemonBattleData>();
|
|
battleData.Battle.Returns(battle);
|
|
battleData.SideIndex.Returns((byte)0);
|
|
battleData.Position.Returns(position);
|
|
target.BattleData.Returns(battleData);
|
|
side.Pokemon.Returns(new IPokemon?[] { target });
|
|
|
|
var hitData = Substitute.For<IHitData>();
|
|
hitData.Damage.Returns(damage);
|
|
move.GetHitData(target, 0).Returns(hitData);
|
|
|
|
return (script, move, target, side, sideScripts, hitData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "On the turn Doom Desire is selected, this attack will do nothing" — the immediate hit
|
|
/// is blocked, and the pending damage is stored as a <see cref="DoomDesireEffect"/> on the target's
|
|
/// side.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task BlockOutgoingHit_FirstUse_BlocksHitAndAddsSideEffect()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, sideScripts, _) = CreateTestSetup();
|
|
var block = false;
|
|
|
|
// Act
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
|
|
// Assert
|
|
await Assert.That(block).IsTrue();
|
|
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<DoomDesireEffect>())).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The pending hit is registered against the target's position, so the effect knows where the damage
|
|
/// must land two turns later.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task BlockOutgoingHit_FirstUse_RegistersTargetPosition()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, sideScripts, _) = CreateTestSetup();
|
|
var block = false;
|
|
|
|
// Act
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
|
|
// Assert
|
|
await Assert.That(sideScripts.TryGet<DoomDesireEffect>(out var effect)).IsTrue();
|
|
await Assert.That(effect!.HasTarget(0)).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: Doom Desire fails when it is already in effect against the target. The second use
|
|
/// fails the hit instead of queueing a second strike.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task BlockOutgoingHit_AlreadyPendingForTarget_FailsHit()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, _, hitData) = CreateTestSetup();
|
|
var block = false;
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
block = false;
|
|
|
|
// Act
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
|
|
// Assert
|
|
hitData.Received(1).Fail();
|
|
await Assert.That(block).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Two turns later, Doom Desire will do damage against the target." One end-of-turn tick
|
|
/// after selection (the selection turn itself) is not enough for the strike to land.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnEndTurn_TwoTurnTicks_DoesNotDamageTargetYet()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, side, sideScripts, _) = CreateTestSetup();
|
|
var block = false;
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
await Assert.That(sideScripts.TryGet<DoomDesireEffect>(out var effect)).IsTrue();
|
|
var battle = Substitute.For<IBattle>();
|
|
|
|
// Act - end of the selection turn and of the first turn after it
|
|
effect!.OnEndTurn(side, battle);
|
|
effect.OnEndTurn(side, battle);
|
|
|
|
// Assert
|
|
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Damage")).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Two turns later, Doom Desire will do damage against the target." At the end of the
|
|
/// second turn after selection the stored damage is dealt to the Pokémon at the registered position.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnEndTurn_ThreeTurnTicks_DamagesTargetWithStoredDamage()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, side, sideScripts, _) = CreateTestSetup(damage: 123);
|
|
var block = false;
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
await Assert.That(sideScripts.TryGet<DoomDesireEffect>(out var effect)).IsTrue();
|
|
var battle = Substitute.For<IBattle>();
|
|
|
|
// Act - selection turn plus the two turns after it
|
|
effect!.OnEndTurn(side, battle);
|
|
effect.OnEndTurn(side, battle);
|
|
effect.OnEndTurn(side, battle);
|
|
|
|
// Assert
|
|
var damageCall = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
|
|
await Assert.That(damageCall).IsNotNull();
|
|
await Assert.That((uint)damageCall!.GetArguments()[0]!).IsEqualTo(123u);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Once the pending strike has landed, the effect has no remaining targets and removes itself from
|
|
/// the side.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnEndTurn_StrikeLanded_EffectRemovesItself()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, side, sideScripts, _) = CreateTestSetup();
|
|
var block = false;
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
await Assert.That(sideScripts.TryGet<DoomDesireEffect>(out var effect)).IsTrue();
|
|
var battle = Substitute.For<IBattle>();
|
|
|
|
// Act
|
|
effect!.OnEndTurn(side, battle);
|
|
effect.OnEndTurn(side, battle);
|
|
effect.OnEndTurn(side, battle);
|
|
|
|
// Assert
|
|
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<DoomDesireEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: without battle data on the target (outside of battle) the script does nothing and
|
|
/// does not throw.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task BlockOutgoingHit_TargetHasNoBattleData_DoesNotBlock()
|
|
{
|
|
// Arrange
|
|
var script = new DoomDesire();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
var target = Substitute.For<IPokemon>();
|
|
target.BattleData.Returns((IPokemonBattleData?)null);
|
|
var block = false;
|
|
|
|
// Act
|
|
script.BlockOutgoingHit(move, target, 0, ref block);
|
|
|
|
// Assert
|
|
await Assert.That(block).IsFalse();
|
|
}
|
|
} |