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;
///
/// Tests for the move script and its .
/// Gen VII Bulbapedia behavior: the user digs underground on the first turn, becoming semi-invulnerable,
/// and attacks on the second turn. "Earthquake, Magnitude, and Fissure hit underground targets (dealing
/// double damage from Gen II onward)".
///
public class DigTests
{
private static (Dig script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice)
CreateTestSetup()
{
var script = new Dig();
var move = Substitute.For();
var user = Substitute.For();
// Use a real script set so the charge volatile added by Dig can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
user.GetScripts().Returns(_ => new ScriptIterator(new List>()));
move.User.Returns(user);
var moveChoice = Substitute.For();
move.MoveChoice.Returns(moveChoice);
var battle = Substitute.For();
battle.EventHook.Returns(new EventHook());
var battleData = Substitute.For();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
return (script, move, user, userVolatile, moveChoice);
}
///
/// Bulbapedia: "The user excavates underground, becoming immune to most moves during the initial turn".
/// On the first turn the move is prevented from executing.
///
[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();
}
///
/// Bulbapedia: the user gains "a semi-invulnerable state" on the initial turn. The charge turn
/// attaches the volatile script to the user.
///
[Test]
public async Task PreventMove_FirstUse_AddsDigEffectToUser()
{
// Arrange
var (script, move, _, userVolatile, _) = CreateTestSetup();
var prevent = false;
// Act
script.PreventMove(move, ref prevent);
// Assert
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsTrue();
}
///
/// The charge turn is marked on the so the knows
/// this choice was the charging turn and does not remove itself for it.
///
[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("dig_charge"), true);
await Assert.That(prevent).IsTrue();
}
///
/// Bulbapedia: "On the following turn, Dig deals damage." When the user already has the
/// from the charge turn, the move is not prevented again.
///
[Test]
public async Task PreventMove_SecondTurn_DoesNotPreventMove()
{
// Arrange
var (script, move, user, userVolatile, _) = CreateTestSetup();
userVolatile.Add(new DigEffect(user));
var prevent = false;
// Act
script.PreventMove(move, ref prevent);
// Assert
await Assert.That(prevent).IsFalse();
}
///
/// Bulbapedia: "On the following turn, Dig deals damage." Once the attack executes, the underground
/// state () is removed from the user.
///
[Test]
public async Task OnBeforeMove_ChargeCompleted_RemovesDigEffect()
{
// Arrange
var (script, move, user, userVolatile, _) = CreateTestSetup();
userVolatile.Add(new DigEffect(user));
// Act
script.OnBeforeMove(move);
// Assert
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsFalse();
}
///
/// Bulbapedia: while underground the user is in "a semi-invulnerable state" and is "immune to most
/// moves". The blocks incoming hits from moves that cannot hit underground
/// Pokémon.
///
[Test]
public async Task BlockIncomingHit_MoveCannotHitUnderground_BlocksHit()
{
// Arrange
var (script, move, user, userVolatile, _) = CreateTestSetup();
var prevent = false;
script.PreventMove(move, ref prevent);
await Assert.That(userVolatile.TryGet(out var effect)).IsTrue();
var incomingMove = Substitute.For();
var incomingMoveData = Substitute.For();
incomingMoveData.HasFlag(new StringKey("hit_underground")).Returns(false);
incomingMove.UseMove.Returns(incomingMoveData);
// Act
var block = false;
effect!.BlockIncomingHit(incomingMove, user, 0, ref block);
// Assert
await Assert.That(block).IsTrue();
}
///
/// Bulbapedia: "Earthquake, Magnitude, and Fissure hit underground targets". Moves flagged as able to
/// hit underground Pokémon are not blocked by the .
///
[Test]
public async Task BlockIncomingHit_EarthquakeLikeMove_DoesNotBlockHit()
{
// Arrange
var (script, move, user, userVolatile, _) = CreateTestSetup();
var prevent = false;
script.PreventMove(move, ref prevent);
await Assert.That(userVolatile.TryGet(out var effect)).IsTrue();
var incomingMove = Substitute.For();
var incomingMoveData = Substitute.For();
incomingMoveData.HasFlag(new StringKey("hit_underground")).Returns(true);
incomingMove.UseMove.Returns(incomingMoveData);
// Act
var block = false;
effect!.BlockIncomingHit(incomingMove, user, 0, ref block);
// Assert
await Assert.That(block).IsFalse();
}
///
/// Bulbapedia: "Earthquake, Magnitude, and Fissure hit underground targets (dealing double damage from
/// Gen II onward)". A move flagged as effective against underground Pokémon deals doubled damage to the
/// underground user.
///
[Test]
public async Task ChangeIncomingMoveDamage_EarthquakeLikeMove_DamageIsDoubled()
{
// Arrange
var (script, move, user, userVolatile, _) = CreateTestSetup();
var prevent = false;
script.PreventMove(move, ref prevent);
await Assert.That(userVolatile.TryGet(out var effect)).IsTrue();
var incomingMove = Substitute.For();
var incomingMoveData = Substitute.For();
incomingMoveData.HasFlag(new StringKey("hit_underground")).Returns(true);
incomingMoveData.HasFlag(new StringKey("effective_against_underground")).Returns(true);
incomingMove.UseMove.Returns(incomingMoveData);
uint damage = 100;
// Act
effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(200u);
}
///
/// Bulbapedia: only Earthquake and Magnitude deal double damage to underground targets. A move without
/// the effective-against-underground flag deals regular damage.
///
[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(out var effect)).IsTrue();
var incomingMove = Substitute.For();
var incomingMoveData = Substitute.For();
incomingMoveData.HasFlag(new StringKey("effective_against_underground")).Returns(false);
incomingMove.UseMove.Returns(incomingMoveData);
uint damage = 100;
// Act
effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(100u);
}
///
/// Bulbapedia: "If disrupted, PP isn't consumed and the move doesn't count as the last used" — when
/// the user's executed choice was not the Dig charge (i.e. the move was disrupted or replaced), the
/// underground state is removed.
///
[Test]
public async Task OnAfterMoveChoice_ChoiceIsNotDigCharge_RemovesDigEffect()
{
// Arrange
var user = Substitute.For();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List>()));
var effect = new DigEffect(user);
userVolatile.Add(effect);
var choice = Substitute.For();
choice.AdditionalData.Returns((Dictionary?)null);
// Act
effect.OnAfterMoveChoice(choice);
// Assert
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsFalse();
}
///
/// The charging turn itself carries the "dig_charge" marker; the must survive
/// that turn so the user stays underground until the attack turn.
///
[Test]
public async Task OnAfterMoveChoice_ChoiceIsDigCharge_KeepsDigEffect()
{
// Arrange
var user = Substitute.For();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List>()));
var effect = new DigEffect(user);
userVolatile.Add(effect);
var choice = Substitute.For();
choice.AdditionalData.Returns(new Dictionary { { "dig_charge", true } });
// Act
effect.OnAfterMoveChoice(choice);
// Assert
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName())).IsTrue();
}
}