390 lines
15 KiB
C#
390 lines
15 KiB
C#
using PkmnLib.Dynamic.Models;
|
|
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.Plugin.Gen7.Scripts.Side;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Species;
|
|
using PkmnLib.Static.Utils;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="HyperspaceFury"/> move script.
|
|
/// Gen VII Bulbapedia behavior: "Hyperspace Fury inflicts damage, then lowers the user's Defense stat by one
|
|
/// stage." "It bypasses accuracy checks to always hit, unless the target is in the semi-invulnerable turn of
|
|
/// a move such as Dig or Fly." "It will also hit the target even if it is protected by Protect, Detect,
|
|
/// Spiky Shield, King's Shield, Mat Block, or Baneful Bunker, and lifts the effects of those moves for the
|
|
/// remainder of the turn. It also removes the effects of Quick Guard, Wide Guard, and Crafty Shield from the
|
|
/// target's side of the field." "It hits through a substitute." "Hyperspace Fury can only be successfully
|
|
/// used by Hoopa as Hoopa Unbound (or a Pokémon that has transformed into Hoopa Unbound)."
|
|
/// </summary>
|
|
public class HyperspaceFuryTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a fully mocked test setup where the target has a real <see cref="ScriptSet"/> as its volatile
|
|
/// script set.
|
|
/// </summary>
|
|
private static (HyperspaceFury script, IExecutingMove move, IPokemon target, IPokemon user, IScriptSet volatileSet)
|
|
CreateTestSetup()
|
|
{
|
|
var script = new HyperspaceFury();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
var target = Substitute.For<IPokemon>();
|
|
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
|
IScriptSet volatileSet = new ScriptSet(target);
|
|
target.Volatile.Returns(volatileSet);
|
|
|
|
var user = Substitute.For<IPokemon>();
|
|
move.User.Returns(user);
|
|
return (script, move, target, user, volatileSet);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extends the test setup with a battle side that has a real <see cref="ScriptSet"/> as its volatile
|
|
/// scripts, so side-wide protections such as <see cref="QuickGuardEffect"/> can be attached.
|
|
/// </summary>
|
|
private static IScriptSet AttachSide(IPokemon target)
|
|
{
|
|
var side = Substitute.For<IBattleSide>();
|
|
side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
|
IScriptSet sideScripts = new ScriptSet(side);
|
|
side.VolatileScripts.Returns(sideScripts);
|
|
|
|
var battle = Substitute.For<IBattle>();
|
|
battle.Sides.Returns(new[] { side });
|
|
|
|
var battleData = Substitute.For<IPokemonBattleData>();
|
|
battleData.Battle.Returns(battle);
|
|
battleData.SideIndex.Returns((byte)0);
|
|
battleData.BattleSide.Returns(side);
|
|
target.BattleData.Returns(battleData);
|
|
|
|
return sideScripts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It will also hit the target even if it is protected by Protect, Detect, Spiky Shield,
|
|
/// King's Shield, Mat Block, or Baneful Bunker, and lifts the effects of those moves for the remainder
|
|
/// of the turn." — the target's <see cref="ProtectionEffectScript"/> (Protect/Detect) is removed.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetUsedProtect_ProtectionEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, volatileSet) = CreateTestSetup();
|
|
volatileSet.Add(new ProtectionEffectScript());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<ProtectionEffectScript>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It will also hit the target even if it is protected by Protect, Detect, Spiky Shield,
|
|
/// King's Shield, Mat Block, or Baneful Bunker, and lifts the effects of those moves for the remainder
|
|
/// of the turn." — a target under Spiky Shield has its <see cref="SpikyShieldEffect"/> removed.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetUnderSpikyShield_ProtectionEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, volatileSet) = CreateTestSetup();
|
|
volatileSet.Add(new SpikyShieldEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<SpikyShieldEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It will also hit the target even if it is protected by Protect, Detect, Spiky Shield,
|
|
/// King's Shield, Mat Block, or Baneful Bunker, and lifts the effects of those moves for the remainder
|
|
/// of the turn." — a target under King's Shield has its <see cref="KingsShieldEffect"/> effect removed.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetUnderKingsShield_ProtectionEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, volatileSet) = CreateTestSetup();
|
|
volatileSet.Add(new KingsShieldEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<KingsShieldEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It will also hit the target even if it is protected by Protect, Detect, Spiky Shield,
|
|
/// King's Shield, Mat Block, or Baneful Bunker, and lifts the effects of those moves for the remainder
|
|
/// of the turn." — a target under Baneful Bunker has its <see cref="BanefulBunkerEffect"/> removed.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetUnderBanefulBunker_ProtectionEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, volatileSet) = CreateTestSetup();
|
|
volatileSet.Add(new BanefulBunkerEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<BanefulBunkerEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// With no protection effect on the target, the secondary effect simply does nothing and does not throw.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_TargetNotProtected_DoesNothing()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, volatileSet) = CreateTestSetup();
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(volatileSet.Count).IsEqualTo(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Hyperspace Fury inflicts damage, then lowers the user's Defense stat by one stage."
|
|
/// The self-inflicted Defense drop is applied via <see cref="IPokemon.ChangeStatBoost"/>.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_DamageDealt_LowersUserDefenseByOneStage()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, user, _) = CreateTestSetup();
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert - the user received a self-inflicted ChangeStatBoost(Defense, -1) call.
|
|
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
|
|
await Assert.That(call).IsNotNull();
|
|
var arguments = call!.GetArguments();
|
|
await Assert.That((Statistic)arguments[0]!).IsEqualTo(Statistic.Defense);
|
|
await Assert.That((sbyte)arguments[1]!).IsEqualTo((sbyte)-1);
|
|
await Assert.That((bool)arguments[2]!).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gives the mocked user an explicit species and form name.
|
|
/// </summary>
|
|
private static void SetUserSpeciesForm(IPokemon user, string speciesName, string formName)
|
|
{
|
|
var species = Substitute.For<ISpecies>();
|
|
species.Name.Returns(new StringKey(speciesName));
|
|
var form = Substitute.For<IForm>();
|
|
form.Name.Returns(new StringKey(formName));
|
|
user.Species.Returns(species);
|
|
user.Form.Returns(form);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Hyperspace Fury can only be successfully used by Hoopa as Hoopa Unbound (or a Pokémon
|
|
/// that has transformed into Hoopa Unbound)." — used by any other Pokémon, the move fails
|
|
/// ("But <Pokémon> can't use the move!").
|
|
/// </summary>
|
|
[Test]
|
|
public async Task FailMove_UserNotHoopaUnbound_MoveFails()
|
|
{
|
|
// Arrange - the user is a different species entirely.
|
|
var (script, move, _, user, _) = CreateTestSetup();
|
|
SetUserSpeciesForm(user, "garchomp", "default");
|
|
var fail = false;
|
|
|
|
// Act
|
|
((IScriptFailMove)script).FailMove(move, ref fail);
|
|
|
|
// Assert
|
|
await Assert.That(fail).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Hyperspace Fury can only be successfully used by Hoopa as Hoopa Unbound" — used by
|
|
/// Hoopa in its Confined (default) form, the move fails ("But Hoopa can't use it the way it is now!").
|
|
/// </summary>
|
|
[Test]
|
|
public async Task FailMove_UserHoopaConfined_MoveFails()
|
|
{
|
|
// Arrange - the user is Hoopa, but in its default (Confined) form.
|
|
var (script, move, _, user, _) = CreateTestSetup();
|
|
SetUserSpeciesForm(user, "hoopa", "default");
|
|
var fail = false;
|
|
|
|
// Act
|
|
((IScriptFailMove)script).FailMove(move, ref fail);
|
|
|
|
// Assert
|
|
await Assert.That(fail).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Hyperspace Fury can only be successfully used by Hoopa as Hoopa Unbound" — used by
|
|
/// Hoopa Unbound itself, the move does not fail.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task FailMove_UserHoopaUnbound_MoveDoesNotFail()
|
|
{
|
|
// Arrange - the user is Hoopa in its Unbound form.
|
|
var (script, move, _, user, _) = CreateTestSetup();
|
|
SetUserSpeciesForm(user, "hoopa", "unbound");
|
|
var fail = false;
|
|
|
|
// Act
|
|
((IScriptFailMove)script).FailMove(move, ref fail);
|
|
|
|
// Assert
|
|
await Assert.That(fail).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It also removes the effects of Quick Guard, Wide Guard, and Crafty Shield from the
|
|
/// target's side of the field." — the <see cref="QuickGuardEffect"/> is removed from the target's side.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_QuickGuardOnTargetSide_SideEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, _) = CreateTestSetup();
|
|
var sideScripts = AttachSide(target);
|
|
sideScripts.Add(new QuickGuardEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<QuickGuardEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It also removes the effects of Quick Guard, Wide Guard, and Crafty Shield from the
|
|
/// target's side of the field." — the <see cref="WideGuardEffect"/> is removed from the target's side.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_WideGuardOnTargetSide_SideEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, _) = CreateTestSetup();
|
|
var sideScripts = AttachSide(target);
|
|
sideScripts.Add(new WideGuardEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<WideGuardEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It also removes the effects of Quick Guard, Wide Guard, and Crafty Shield from the
|
|
/// target's side of the field." — the <see cref="CraftyShieldEffect"/> is removed from the target's side.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_CraftyShieldOnTargetSide_SideEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, _) = CreateTestSetup();
|
|
var sideScripts = AttachSide(target);
|
|
sideScripts.Add(new CraftyShieldEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<CraftyShieldEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It will also hit the target even if it is protected by Protect, Detect, Spiky Shield,
|
|
/// King's Shield, Mat Block, or Baneful Bunker, and lifts the effects of those moves for the remainder
|
|
/// of the turn." — Mat Block is a side-wide effect in this implementation
|
|
/// (<see cref="MatBlockEffect"/>), and it is lifted as well.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_MatBlockOnTargetSide_SideEffectRemoved()
|
|
{
|
|
// Arrange
|
|
var (script, move, target, _, _) = CreateTestSetup();
|
|
var sideScripts = AttachSide(target);
|
|
sideScripts.Add(new MatBlockEffect());
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, target, 0);
|
|
|
|
// Assert
|
|
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<MatBlockEffect>())).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It bypasses accuracy checks to always hit, unless the target is in the semi-invulnerable
|
|
/// turn of a move such as Dig or Fly."
|
|
/// Integration check: the Gen7 data gives hyperspace_fury an accuracy of 255, which the engine treats as
|
|
/// an unmodifiable always-hit.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task MoveData_HyperspaceFury_BypassesAccuracyChecks()
|
|
{
|
|
// Arrange
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
|
|
// Act
|
|
var found = library.StaticLibrary.Moves.TryGet("hyperspace_fury", out var moveData);
|
|
|
|
// Assert
|
|
await Assert.That(found).IsTrue();
|
|
await Assert.That(moveData!.Accuracy).IsEqualTo((byte)255);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It hits through a substitute."
|
|
/// Integration check: the Gen7 data gives hyperspace_fury the <see cref="MoveFlags.IgnoreSubstitute"/>
|
|
/// flag.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task MoveData_HyperspaceFury_IgnoresSubstitute()
|
|
{
|
|
// Arrange
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
|
|
// Act
|
|
var found = library.StaticLibrary.Moves.TryGet("hyperspace_fury", out var moveData);
|
|
|
|
// Assert
|
|
await Assert.That(found).IsTrue();
|
|
await Assert.That(moveData!.HasFlag(MoveFlags.IgnoreSubstitute)).IsTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "It will also hit the target even if it is protected by Protect, Detect, Spiky Shield,
|
|
/// King's Shield, Mat Block, or Baneful Bunker".
|
|
/// Integration check: <see cref="ProtectionEffectScript.BlockIncomingHit"/> blocks any move carrying the
|
|
/// <see cref="MoveFlags.Protect"/> flag before <see cref="HyperspaceFury.OnSecondaryEffect"/> can run,
|
|
/// so hyperspace_fury's data must not carry that flag for the move to hit protected targets.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task MoveData_HyperspaceFury_NotBlockedByProtection()
|
|
{
|
|
// Arrange
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
|
|
// Act
|
|
var found = library.StaticLibrary.Moves.TryGet("hyperspace_fury", out var moveData);
|
|
|
|
// Assert
|
|
await Assert.That(found).IsTrue();
|
|
await Assert.That(moveData!.HasFlag(MoveFlags.Protect)).IsFalse();
|
|
}
|
|
} |