More unit tests, fixes
This commit is contained in:
327
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/BounceTests.cs
Normal file
327
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/BounceTests.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
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.Common;
|
||||
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="Bounce"/> move script.
|
||||
/// Gen VII Bulbapedia behavior: "The user gains the Sky-High status on the turn this move is used, then
|
||||
/// attacks on the following turn, inflicting damage with a 30% chance of paralyzing the target. While in
|
||||
/// Sky-High status, the user is invulnerable to most moves, but can still be hit by Gust, Twister, Thunder,
|
||||
/// and Sky Uppercut", where (from Generation V onward) for Gust and Twister "the damage dealt will be doubled".
|
||||
/// </summary>
|
||||
public class BounceTests
|
||||
{
|
||||
private static (Bounce script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice,
|
||||
IBattleRandom random) CreateTestSetup()
|
||||
{
|
||||
var script = new Bounce();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
// Use a real script set so the charge volatile added by Bounce 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>();
|
||||
var random = Substitute.For<IBattleRandom>();
|
||||
battle.Random.Returns(random);
|
||||
battle.EventHook.Returns(new EventHook());
|
||||
var battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.Battle.Returns(battle);
|
||||
user.BattleData.Returns(battleData);
|
||||
|
||||
return (script, move, user, userVolatile, moveChoice, random);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "The user gains the Sky-High 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 Sky-High status on the turn this move is used".
|
||||
/// The charge turn attaches the <see cref="ChargeBounceEffect"/> volatile script to the user.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task PreventMove_FirstUse_AddsChargeBounceEffectToUser()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, userVolatile, _, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
|
||||
// Act
|
||||
script.PreventMove(move, ref prevent);
|
||||
|
||||
// Assert
|
||||
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<ChargeBounceEffect>())).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "The user gains the Sky-High status on the turn this move is used".
|
||||
/// The charge turn is marked on the <see cref="IMoveChoice"/> so the <see cref="ChargeBounceEffect"/>
|
||||
/// knows this choice was the charging turn.
|
||||
/// </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("bounce_charge"), true);
|
||||
await Assert.That(prevent).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "then attacks on the following turn". When the user already has the
|
||||
/// <see cref="ChargeBounceEffect"/> 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 ChargeBounceEffect(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 executes, the Sky-High state
|
||||
/// (<see cref="ChargeBounceEffect"/>) is removed from the user.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnBeforeMove_ChargeCompleted_RemovesChargeBounceEffect()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _, _) = CreateTestSetup();
|
||||
userVolatile.Add(new ChargeBounceEffect(user));
|
||||
|
||||
// Act
|
||||
script.OnBeforeMove(move);
|
||||
|
||||
// Assert
|
||||
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<ChargeBounceEffect>())).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "inflicting damage with a 30% chance of paralyzing the target."
|
||||
/// When the effect chance roll succeeds, the target is paralyzed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_EffectChanceSucceeds_ParalyzesTarget()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, _, _, random) = CreateTestSetup();
|
||||
var target = Substitute.For<IPokemon>();
|
||||
random.EffectChance(30, move, target, 0).Returns(true);
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
target.Received(1).SetStatus(new StringKey("paralyzed"), user);
|
||||
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "inflicting damage with a 30% chance of paralyzing the target."
|
||||
/// When the effect chance roll fails, the target is not paralyzed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_EffectChanceFails_DoesNotParalyzeTarget()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, _, _, random) = CreateTestSetup();
|
||||
var target = Substitute.For<IPokemon>();
|
||||
random.EffectChance(30, move, target, 0).Returns(false);
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "a 30% chance of paralyzing the target."
|
||||
/// The paralysis roll is made with exactly a 30% chance.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_ParalysisRoll_UsesThirtyPercentChance()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, _, _, random) = CreateTestSetup();
|
||||
var target = Substitute.For<IPokemon>();
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
random.Received(1).EffectChance(30, move, target, 0);
|
||||
await Assert.That(random.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "EffectChance")).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Technical test: outside of battle (no battle data) the secondary effect does nothing and does not throw.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_NoBattleData_DoesNotParalyzeTarget()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, _, _, _) = CreateTestSetup();
|
||||
user.BattleData.Returns((IPokemonBattleData?)null);
|
||||
var target = Substitute.For<IPokemon>();
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "While in Sky-High status, the user is invulnerable to most moves".
|
||||
/// The <see cref="ChargeBounceEffect"/> added by the charge turn blocks incoming hits from moves that
|
||||
/// cannot hit semi-invulnerable flying Pokémon.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task BlockIncomingHit_MoveCannotHitSkyHighUser_BlocksHit()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<ChargeBounceEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(MoveFlags.HitFlying).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 Gust, Twister, Thunder, and Sky Uppercut".
|
||||
/// Moves flagged as able to hit semi-invulnerable flying Pokémon are not blocked by the
|
||||
/// <see cref="ChargeBounceEffect"/>.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task BlockIncomingHit_MoveCanHitSkyHighUser_DoesNotBlockHit()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<ChargeBounceEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(MoveFlags.HitFlying).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 (Generation V onward): "If the user is hit by Gust or Twister while the user has the
|
||||
/// Sky-High status, the damage dealt will be doubled". Gust and Twister carry the
|
||||
/// <see cref="MoveFlags.EffectiveAgainstFly"/> flag, so their damage against the Sky-High user doubles.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeIncomingMoveDamage_GustLikeMove_DamageIsDoubled()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<ChargeBounceEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(MoveFlags.HitFlying).Returns(true);
|
||||
incomingMoveData.HasFlag(MoveFlags.EffectiveAgainstFly).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 (Generation V onward): only for Gust and Twister "the damage dealt will be doubled".
|
||||
/// Other moves that can hit the Sky-High user (e.g. Thunder, Sky Uppercut) deal regular damage; they do
|
||||
/// not carry the <see cref="MoveFlags.EffectiveAgainstFly"/> flag.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeIncomingMoveDamage_ThunderLikeMove_DamageIsUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, userVolatile, _, _) = CreateTestSetup();
|
||||
var prevent = false;
|
||||
script.PreventMove(move, ref prevent);
|
||||
await Assert.That(userVolatile.TryGet<ChargeBounceEffect>(out var effect)).IsTrue();
|
||||
|
||||
var incomingMove = Substitute.For<IExecutingMove>();
|
||||
var incomingMoveData = Substitute.For<IMoveData>();
|
||||
incomingMoveData.HasFlag(MoveFlags.HitFlying).Returns(true);
|
||||
incomingMoveData.HasFlag(MoveFlags.EffectiveAgainstFly).Returns(false);
|
||||
incomingMove.UseMove.Returns(incomingMoveData);
|
||||
uint damage = 100;
|
||||
|
||||
// Act
|
||||
effect!.ChangeIncomingMoveDamage(incomingMove, user, 0, ref damage);
|
||||
|
||||
// Assert
|
||||
await Assert.That(damage).IsEqualTo(100u);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user