227 lines
8.5 KiB
C#
227 lines
8.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.Pokemon;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Utils;
|
|
using MistyTerrainScript = PkmnLib.Plugin.Gen7.Scripts.Terrain.MistyTerrain;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="MistyTerrain"/> move script and the
|
|
/// <see cref="MistyTerrainScript"/> terrain script it activates.
|
|
/// Gen VII Bulbapedia behavior: "Misty Terrain creates terrain that envelops the field and replaces the
|
|
/// background environment and any other terrain that is already in effect." "This terrain has the following
|
|
/// effects on Pokémon that are on the ground [...]: It halves the power of Dragon-type moves used against
|
|
/// affected Pokémon [...]. It prevents affected Pokémon from being afflicted by non-volatile status
|
|
/// conditions". Generation VII: "Pokémon affected by Misty Terrain can no longer become confused."
|
|
/// </summary>
|
|
public class MistyTerrainTests
|
|
{
|
|
private static (MistyTerrain script, IExecutingMove move, IBattle battle) CreateMoveTestSetup()
|
|
{
|
|
var script = new MistyTerrain();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
|
|
var battle = Substitute.For<IBattle>();
|
|
|
|
var user = Substitute.For<IBattlePokemon>();
|
|
move.User.Returns(user);
|
|
user.Battle.Returns(battle);
|
|
|
|
return (script, move, battle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a mocked setup for the terrain script's base power hook, with a target that is either
|
|
/// grounded or floating and a hit of the given type (null for a typeless hit).
|
|
/// </summary>
|
|
private static (MistyTerrainScript terrain, IExecutingMove move, IBattlePokemon target) CreateTerrainSetup(
|
|
bool targetFloating, string? moveType)
|
|
{
|
|
var terrain = new MistyTerrainScript();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
var target = Substitute.For<IBattlePokemon>();
|
|
target.IsFloating.Returns(targetFloating);
|
|
|
|
var hitData = Substitute.For<IHitData>();
|
|
hitData.Type.Returns(moveType == null ? null : new TypeIdentifier(1, new StringKey(moveType)));
|
|
move.GetHitData(target, 0).Returns(hitData);
|
|
|
|
return (terrain, move, target);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Misty Terrain creates terrain that envelops the field and replaces the background
|
|
/// environment and any other terrain that is already in effect." — the move sets the battle's terrain to
|
|
/// the <see cref="MistyTerrainScript"/> terrain script.
|
|
/// </summary>
|
|
[Test]
|
|
public void OnSecondaryEffect_SetsMistyTerrainOnBattle()
|
|
{
|
|
// Arrange
|
|
var (script, move, battle) = CreateMoveTestSetup();
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
|
|
|
|
// Assert
|
|
battle.Received(1).SetTerrain(ScriptUtils.ResolveName<MistyTerrainScript>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It halves the power of Dragon-type moves used against affected Pokémon (regardless of
|
|
/// whether the user of the move is affected by Misty Terrain)." Halving uses integer division.
|
|
/// </summary>
|
|
[Test, Arguments((ushort)100, (ushort)50), Arguments((ushort)95, (ushort)47), Arguments((ushort)1, (ushort)0),
|
|
Arguments((ushort)65535, (ushort)32767)]
|
|
public async Task ChangeBasePower_DragonMoveAgainstGroundedPokemon_PowerHalved(ushort basePower, ushort expected)
|
|
{
|
|
// Arrange
|
|
var (terrain, move, target) = CreateTerrainSetup(false, "dragon");
|
|
|
|
// Act
|
|
terrain.ChangeBasePower(move, target, 0, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo(expected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the terrain only affects "Pokémon that are on the ground" — a Dragon-type move used
|
|
/// against a floating (airborne) Pokémon keeps its full power.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeBasePower_DragonMoveAgainstFloatingPokemon_PowerUnchanged()
|
|
{
|
|
// Arrange
|
|
var (terrain, move, target) = CreateTerrainSetup(true, "dragon");
|
|
ushort basePower = 100;
|
|
|
|
// Act
|
|
terrain.ChangeBasePower(move, target, 0, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo((ushort)100);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: only "Dragon-type moves" have their power halved — moves of any other type are
|
|
/// unaffected.
|
|
/// </summary>
|
|
[Test, Arguments("fairy"), Arguments("normal")]
|
|
public async Task ChangeBasePower_NonDragonMove_PowerUnchanged(string type)
|
|
{
|
|
// Arrange
|
|
var (terrain, move, target) = CreateTerrainSetup(false, type);
|
|
ushort basePower = 100;
|
|
|
|
// Act
|
|
terrain.ChangeBasePower(move, target, 0, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo((ushort)100);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: a typeless hit (null hit type) is not a Dragon-type move, so its power is unchanged
|
|
/// and the script does not throw.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeBasePower_TypelessMove_PowerUnchanged()
|
|
{
|
|
// Arrange
|
|
var (terrain, move, target) = CreateTerrainSetup(false, null);
|
|
ushort basePower = 100;
|
|
|
|
// Act
|
|
terrain.ChangeBasePower(move, target, 0, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo((ushort)100);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It prevents affected Pokémon from being afflicted by non-volatile status conditions" —
|
|
/// a grounded Pokémon cannot be given any non-volatile status while Misty Terrain is active.
|
|
/// </summary>
|
|
[Test, Arguments("burned"), Arguments("paralyzed"), Arguments("poisoned"), Arguments("frozen"), Arguments("sleep")]
|
|
public async Task PreventStatusChange_GroundedPokemon_StatusPrevented(string status)
|
|
{
|
|
// Arrange
|
|
var terrain = new MistyTerrainScript();
|
|
var pokemon = Substitute.For<IBattlePokemon>();
|
|
pokemon.IsFloating.Returns(false);
|
|
var preventStatus = false;
|
|
|
|
// Act
|
|
terrain.PreventStatusChange(pokemon, new StringKey(status), false, ref preventStatus);
|
|
|
|
// Assert
|
|
await Assert.That(preventStatus).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Rest will fail if used by an affected Pokémon." — even a self-inflicted status (Rest's
|
|
/// sleep) is prevented for grounded Pokémon.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task PreventStatusChange_SelfInflictedSleep_Prevented()
|
|
{
|
|
// Arrange
|
|
var terrain = new MistyTerrainScript();
|
|
var pokemon = Substitute.For<IBattlePokemon>();
|
|
pokemon.IsFloating.Returns(false);
|
|
var preventStatus = false;
|
|
|
|
// Act
|
|
terrain.PreventStatusChange(pokemon, new StringKey("sleep"), true, ref preventStatus);
|
|
|
|
// Assert
|
|
await Assert.That(preventStatus).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the terrain only affects "Pokémon that are on the ground" — a floating (airborne)
|
|
/// Pokémon can still be afflicted by non-volatile status conditions.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task PreventStatusChange_FloatingPokemon_StatusNotPrevented()
|
|
{
|
|
// Arrange
|
|
var terrain = new MistyTerrainScript();
|
|
var pokemon = Substitute.For<IBattlePokemon>();
|
|
pokemon.IsFloating.Returns(true);
|
|
var preventStatus = false;
|
|
|
|
// Act
|
|
terrain.PreventStatusChange(pokemon, new StringKey("burned"), false, ref preventStatus);
|
|
|
|
// Assert
|
|
await Assert.That(preventStatus).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia (Generation VII): "Pokémon affected by Misty Terrain can no longer become confused." —
|
|
/// adding the <see cref="Confusion"/> volatile to a grounded Pokémon is prevented while Misty Terrain
|
|
/// is in effect.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task PreventVolatileAdd_ConfusionOnGroundedPokemon_Prevented()
|
|
{
|
|
// Arrange
|
|
var terrain = new MistyTerrainScript();
|
|
var pokemon = Substitute.For<IBattlePokemon>();
|
|
pokemon.IsFloating.Returns(false);
|
|
var preventVolatileAdd = false;
|
|
|
|
// Act - a correct implementation would prevent the Confusion volatile through this hook.
|
|
if (terrain is IScriptPreventVolatileAdd preventHook)
|
|
preventHook.PreventVolatileAdd(pokemon, new Confusion(), ref preventVolatileAdd);
|
|
|
|
// Assert
|
|
await Assert.That(preventVolatileAdd).IsTrue();
|
|
}
|
|
} |