More unit tests, more fixes
This commit is contained in:
288
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/DiveTests.cs
Normal file
288
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/DiveTests.cs
Normal file
@@ -0,0 +1,288 @@
|
||||
using PkmnLib.Dynamic.Events;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.Models.Choices;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
|
||||
using PkmnLib.Static.Moves;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <see cref="Dive"/> move script and its <see cref="DiveEffect"/>.
|
||||
/// Gen VII Bulbapedia behavior: "The user gains the Submerged status on the turn this move is used, then
|
||||
/// attacks on the following turn. While submerged, the user is invulnerable to most moves but can still be
|
||||
/// hit by Surf and Whirlpool, dealing twice the regular damage."
|
||||
/// </summary>
|
||||
public class DiveTests
|
||||
{
|
||||
private static (Dive script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice)
|
||||
CreateTestSetup()
|
||||
{
|
||||
var script = new Dive();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
// Use a real script set so the charge volatile added by Dive can be inspected afterwards.
|
||||
var userVolatile = new ScriptSet(user);
|
||||
user.Volatile.Returns(userVolatile);
|
||||
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
||||
move.User.Returns(user);
|
||||
|
||||
var moveChoice = Substitute.For<IMoveChoice>();
|
||||
move.MoveChoice.Returns(moveChoice);
|
||||
|
||||
var battle = Substitute.For<IBattle>();
|
||||
battle.EventHook.Returns(new EventHook());
|
||||
var battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.Battle.Returns(battle);
|
||||
user.BattleData.Returns(battleData);
|
||||
|
||||
return (script, move, user, userVolatile, moveChoice);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "The user gains the Submerged status on the turn this move is used, then attacks on the
|
||||
/// following turn." On the first turn the move is prevented from executing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task PreventMove_FirstUse_PreventsMoveForChargeTurn()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, _, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
|
||||
// Act
|
||||
script.PreventMove(move, ref prevent);
|
||||
|
||||
// Assert
|
||||
await Assert.That(prevent).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "The user gains the Submerged status on the turn this move is used." The charge turn
|
||||
/// attaches the <see cref="DiveEffect"/> volatile script to the user.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task PreventMove_FirstUse_AddsDiveEffectToUser()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, userVolatile, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
|
||||
// Act
|
||||
script.PreventMove(move, ref prevent);
|
||||
|
||||
// Assert
|
||||
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<DiveEffect>())).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The charge turn is marked on the <see cref="IMoveChoice"/> so the <see cref="DiveEffect"/> knows
|
||||
/// this choice was the charging turn and does not remove itself for it.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task PreventMove_FirstUse_MarksMoveChoiceAsChargeTurn()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, _, moveChoice) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
|
||||
// Act
|
||||
script.PreventMove(move, ref prevent);
|
||||
|
||||
// Assert
|
||||
moveChoice.Received(1).SetAdditionalData(new StringKey("dive_charge"), true);
|
||||
await Assert.That(prevent).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "then attacks on the following turn." When the user already has the
|
||||
/// <see cref="DiveEffect"/> from the charge turn, the move is not prevented again.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task PreventMove_SecondTurn_DoesNotPreventMove()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _) = CreateTestSetup();
|
||||
userVolatile.Add(new DiveEffect(user));
|
||||
var prevent = false;
|
||||
|
||||
// Act
|
||||
script.PreventMove(move, ref prevent);
|
||||
|
||||
// Assert
|
||||
await Assert.That(prevent).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "then attacks on the following turn." Once the attack lands, the Submerged state
|
||||
/// (<see cref="DiveEffect"/>) is removed from the user.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_AttackExecuted_RemovesDiveEffect()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _) = CreateTestSetup();
|
||||
userVolatile.Add(new DiveEffect(user));
|
||||
var target = Substitute.For<IPokemon>();
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<DiveEffect>())).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "While submerged, the user is invulnerable to most moves". The <see cref="DiveEffect"/>
|
||||
/// blocks incoming hits from moves that cannot hit underwater Pokémon.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task BlockIncomingHit_MoveCannotHitUnderwater_BlocksHit()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<DiveEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(new StringKey("hit_underwater")).Returns(false);
|
||||
incomingMove.UseMove.Returns(incomingMoveData);
|
||||
|
||||
// Act
|
||||
var block = false;
|
||||
effect!.BlockIncomingHit(incomingMove, user, 0, ref block);
|
||||
|
||||
// Assert
|
||||
await Assert.That(block).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "but can still be hit by Surf and Whirlpool". Moves flagged as able to hit underwater
|
||||
/// Pokémon are not blocked by the <see cref="DiveEffect"/>.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task BlockIncomingHit_SurfLikeMove_DoesNotBlockHit()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<DiveEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(new StringKey("hit_underwater")).Returns(true);
|
||||
incomingMove.UseMove.Returns(incomingMoveData);
|
||||
|
||||
// Act
|
||||
var block = false;
|
||||
effect!.BlockIncomingHit(incomingMove, user, 0, ref block);
|
||||
|
||||
// Assert
|
||||
await Assert.That(block).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "can still be hit by Surf and Whirlpool, dealing twice the regular damage." A move
|
||||
/// flagged as effective against underwater Pokémon deals doubled damage to the submerged user.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeIncomingMoveDamage_SurfLikeMove_DamageIsDoubled()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<DiveEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(new StringKey("hit_underwater")).Returns(true);
|
||||
incomingMoveData.HasFlag(new StringKey("effective_against_underwater")).Returns(true);
|
||||
incomingMove.UseMove.Returns(incomingMoveData);
|
||||
uint damage = 100;
|
||||
|
||||
// Act
|
||||
effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage);
|
||||
|
||||
// Assert
|
||||
await Assert.That(damage).IsEqualTo(200u);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: only Surf and Whirlpool deal "twice the regular damage" to the submerged user. A move
|
||||
/// without the effective-against-underwater flag deals regular damage.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeIncomingMoveDamage_RegularMove_DamageIsUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<DiveEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(new StringKey("effective_against_underwater")).Returns(false);
|
||||
incomingMove.UseMove.Returns(incomingMoveData);
|
||||
uint damage = 100;
|
||||
|
||||
// Act
|
||||
effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage);
|
||||
|
||||
// Assert
|
||||
await Assert.That(damage).IsEqualTo(100u);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the user's executed choice was not the Dive charge (i.e. the move was disrupted or replaced),
|
||||
/// the Submerged state is removed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnAfterMoveChoice_ChoiceIsNotDiveCharge_RemovesDiveEffect()
|
||||
{
|
||||
// Arrange
|
||||
var user = Substitute.For<IPokemon>();
|
||||
var userVolatile = new ScriptSet(user);
|
||||
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
||||
var effect = new DiveEffect(user);
|
||||
userVolatile.Add(effect);
|
||||
var choice = Substitute.For<IMoveChoice>();
|
||||
choice.AdditionalData.Returns((Dictionary<StringKey, object?>?)null);
|
||||
|
||||
// Act
|
||||
effect.OnAfterMoveChoice(choice);
|
||||
|
||||
// Assert
|
||||
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<DiveEffect>())).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The charging turn itself carries the "dive_charge" marker; the <see cref="DiveEffect"/> must
|
||||
/// survive that turn so the user stays submerged until the attack turn.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnAfterMoveChoice_ChoiceIsDiveCharge_KeepsDiveEffect()
|
||||
{
|
||||
// Arrange
|
||||
var user = Substitute.For<IPokemon>();
|
||||
var userVolatile = new ScriptSet(user);
|
||||
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
|
||||
var effect = new DiveEffect(user);
|
||||
userVolatile.Add(effect);
|
||||
var choice = Substitute.For<IMoveChoice>();
|
||||
choice.AdditionalData.Returns(new Dictionary<StringKey, object?> { { "dive_charge", true } });
|
||||
|
||||
// Act
|
||||
effect.OnAfterMoveChoice(choice);
|
||||
|
||||
// Assert
|
||||
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<DiveEffect>())).IsTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user